home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
TeX 1995 July
/
TeX CD-ROM July 1995 (Disc 1)(Walnut Creek)(1995).ISO
/
dviware
/
ln03
/
rmcs
/
dvitoln03.web
< prev
next >
Wrap
Text File
|
1991-09-29
|
451KB
|
10,434 lines
% This program by D. E. Knuth is not copyrighted and can be used freely.
% Version 1 was completed in September, 1982.
% Slight changes were made in October, 1982, for version 0.7 of TeX.
% Version 1.1 corrected minor bugs (May, 1983).
% Version 2 was released with version 0.999 of TeX (July, 1983).
% Version 2.1 corrected a bug when no fonts are present (September, 1983).
% Version 2.2 corrected bugs in max_*_so_far and put1 (October, 1983).
% Version 2.3 corrected spacing of accents (March, 1984).
% Version 2.4 fixed rounding, changed oriental font conventions (April, 1984).
% Version 2.5 improved the case of zero pages (May, 1984).
% Version 2.6 introduced max_drift (June, 1984).
% Version 2.7 had minor editorial changes (August, 1984).
% Version 1.0 of DVItoLN03 was created (as a change file to DVItype) March 1986
% by Brian HAMILTON KELLY, Royal Military College of Science,
% Shrivenham, SWINDON, SN6 8LA, United Kingdom
% Tel: Swindon (0793) 785252 (Int'l +44-793-785252)
% e-mail: (Janet) tex@@uk.ac.cranfield.rmcs
%
% This drew heavily upon Flavio Rose's DVI2LNG program, but also
% included the work formerly performed as a separate pass by his
% LN03TOPP program (which had bugs, and was written in PL/I, for
% which we did not have a compiler!)
% Version 1.1 corrected minor bugs in the port from PL/I and displayed page
% numbers as each was processed April 1986
% Version 1.2 used .PXL files in separate directories for each magnification,
% in the manner of Andrew Trevorrow's DVItoVDU July 1986
% Version 1.3 introduced minor cosmetic changes and increased |max_rules| to
% 2048 July 1987
% Version 1.4 added support for including sixel graphics
% through \special Sep 87
% Version 1.5 merged the original DVItype.WEB and the (now hopelessly huge)
% change file, and `WEBified' the whole system. Also added proper
% support for default filenames, so that one CAN reference an
% older DVI file than the latest version.
% Version 1.6 supports oversized glyphs (by making them into invisible
% characters) and also outputs invisible characters as a single
% pixel high row of white pixels of an appropriate width
% Version 1.7 prints the glyphs of oversized characters as bitmap sixel dumps.
% Version 2.0 reads PK files instead of .PXL (if available)
% Version 2.1 corrects error reading invisible fonts from packed files and
% with invisible characters of zero TFM width.
% Version 3.0 includes wholesale rearrangement of some sections of code, to
% improve its WEB-like quality. It also adds the following
% functionality:
% The packed and unpacked font files may be provided in
% either a flat or a rooted directory structure; if logical
% names are used to specify these locations (as in the .CLD
% file provided), the files may be spread over a number of
% different directories or volumes.
% The error messages are improved over earlier versions of
% the program, and are now all indexed in the woven
% (WEAVEd?) WEB.
% The program can now handle fonts with more then 128
% characters, up to TeX's limit of 256. Therefore, it can
% now process documents which use Silvio Levy's Greek fonts.
% Retention of the log (.TYP) file may now be forced,
% suppressed, or allowed to be determined by the success of
% the processing.
% Minor revisions and corrections have been made, in
% particular, it now correctly understands the physical
% limitations to the imaging area.
% Version 3.1 correctly computes the magnification of each font for the font
% loading reports, and also ensures that all output files are
% opened in the current default directory, even when logical
% names are used for the input file. It also extracts and
% reports the full file specification of the source (.DVI) file.
% Also introduced the /OUTPUT qualifier and file spec on /LOG.
% Version 3.1-1 adds the `special' commands that were in the original Rose
% program; this code was developed independently of (and
% simultaneously with) version 3.1 (see above), and it therefore
% seemed appropriate to give it a DEC-style version number
% extension...
% Version 3.1-2 increases |max_rules| to 16384 to cope with the output of
% BIGTeX.
% Version 3.1-3 reports an error status if the opening of a \special sixel
% file fails. The LN03 is also reset to pixel spacing units
% after the sixel file has been included.
% Version 3.1-4 avoids attempting to print non-existent glyphs; some font
% designers use non-existent characters in ligature sequences
% (for example, Georgia Tobin ligatures * with characters such
% as i, j, r, s, to select a variant form of the letter).
% With earlier versions of this program, having failed to
% download a glyph for the non-existent character, an attempt
% would be made to print it is a bitmap when processing the
% pages.
% Version 3.2 This version, and all others prior to V4.0, were never
% released: they were purely internal to RMCS whilst
% additional functionality was being incorporated.
% This version tidied up Robin Fairbairns' code, particularly
% for recognizing <dimen> units.
% Version 3.2-1 Correct code so that the arguments of \special are treated
% as |ASCII_code| (they are not *necessarily* |char|s).
% Version 3.3 Provide qualifiers to define all logicals used, so that
% sites may chose to use TEX$FONTS or TEX_FONTS, etc.
% Permit use of units other than pixel for specification
% of /LEFT_MARGIN and /TOP_MARGIN.
% Version 3.3-1 Permit /LEFT_MARGIN and /TOP_MARGIN to take dimensionless
% quantity, with the assumption that it's in pixels.
% Version 3.4 Look out for virtual fonts, and reject them
% Version 3.4-1 Read VF file, and parse it
% Version 3.4-2 Map virtual font characters to real fonts, and load them
% Version 3.4-3 Now able to print simple characters from a VF
% Version 3.5 Made do_page capable of recursion to support complex
% sequences in VF files
% Version 3.5-1 Revised DVI reading code for input from |vf|
% Version 3.5-2 Scaled |dvi| movements taken from VF file
% Version 3.5-3 Corrected computation of scaling of -ve movements
% Version 3.5-4 Removed confusion caused by empty segments on |seg_list|
% Version 3.5-5 Use font_scaled_size[vfno] for scaling physical fonts!
% Version 3.5-6 Corrected storage of -ve parameters in |vf| array
% Version 3.5-7 Terminate normal text segment preceding composite character
% Version 3.6 Read TFM files only once; fold upper/lower case names
% Version 3.6-1 Guard against infinite recursion with font recurse
% Version 3.6-2 Reference to another virtual font can't map to LN03 font
% Version 3.6-3 Correct spurious checksum error
% Version 3.7 Supports landscape and portrait \specials
% Version 3.7-1 Defers bitmap output of glyphs until |eop|, like rest
% Version 3.7-2 Correct handling of bitmap glyphs in |out_list|
% Version 3.8 Support mixture of orientations on same page
% Version 3.8-1 Moved all |max...| definitions to near front of WEB
% Version 3.8-2 Changed |varying [name_size] of char| into |file_spec|
% Version 3.8-3 Included narrative describing TFM files
% Version 3.8-4 Aggregate all |glyph_info| into one vector |glyphs|
% Version 3.8-5 Cosmetic changes; errors/warnings not just to TYP file
% Version 3.8-6 Preliminary moves to set rules for missing fonts
% Version 3.9 Image glyph from missing font by a solid rule
% Version 3.9-1 Integrate |pixel_width|, etc into |glyph_map|
% Version 3.9-2 Integrate |width|, etc into |glyph_map|
% Version 3.9-3 Further speed-up, by greater use of |with glyph_map|
% Version 3.9-4 Speed up setting loop, by removing double indexing
% Version 3.9-5 Defer text too wide message until eop; report phases
% Version 3.9-6 Couple more little speed-ups, missed before
% Version 3.9-7 Cosmetic amendments; improved indexing
% Version 3.9-8 Move <Move to top of form> before Copy_sixel_file
% Version 3.91 Start unravelling |print_mode| and trays at top of form
% Version 3.91-1 Acknowledge efforts of Robin and Karsten
% Version 3.91-2 Ensure |last_page_recto| set even first time through
% Version 3.91-3 Use Karsten's |blank_follows| mechanism
% Version 3.91-4 Trim trailing spaces from |cur_name| in error message
% Version 3.91-5 Determine |ln03_master| from /DEVICE_TYPE setting
% Version 3.91-6 Provide |rd_scaled_pt|, for decoding /hfuzz, /vfuzz
% Version 3.91-7 In duplex, don't change tray until 3rd page
% Version 3.91-8 Insert sixel dumps into |seg_list|
% Version 4.0 Cosmetic changes to error messages - new release!!!
\outer\def\N#1.#2.{\MN#1.\vfil\eject % beginning of starred section
\def\rhead{\uppercase{\ignorespaces#2}} % define running headline
\message{*\modno} % progress report
{\def\.{\string\.}\def\BS{\string\BS\space}% Prevent expansion during contents write
\edef\next{\write\cont{\Z{#2}{\modno}{\the\pageno}}}\next % to contents file
}%
\ifon\startsection{\bf\ignorespaces#2.\quad}\ignorespaces}
% Here is TeX material that gets inserted after \input webmac
\def\hang{\hangindent 3em\indent\ignorespaces}
\font\ninerm=cmr9
\let\mc=\ninerm % medium caps for names like PASCAL
\font\logo=logo10 % font used for the METAFONT logo
\def\MF{{\logo META}\-{\logo FONT}}
\def\PASCAL{P{\mc ASCAL}}
\def\(#1){} % this is used to make section names sort themselves better
\def\9#1{} % this is used for sort keys in the index
\def\title{DVI$\,$\lowercase{to}$\,$LN03 V4.0}
\def\contentspagenumber{0}
\def\topofcontents{\null
\def\titlepage{F} % include headline on the contents page
\def\rheader{\mainfont\hfil \contentspagenumber}
\vfill
\centerline{\titlefont The {\ttitlefont DVItoLN03} processor}
\vskip 15pt
\centerline{(Version 4.0, 27th February 1991)}
\centerline{(Vax/VMS Version)}
\vfill}
\def\botofcontents{\vfill
\centerline{\mc Acknowledgements}
\centerline{\hsize 5in\baselineskip9pt
\vbox{\ninerm\noindent
This program was originally based upon Flavio Rose's change file
which, applied to David Fuch's original \.{DVItype} program, generated
his \.{DVI2LNG} program. (The output produced by \.{DVI2LNG} was then
further processed by \.{LN03TOPP} to yield a \.{LN3} file to drive a
Digital LN03 laser printer.) Further extensions and enhancements were
added, and when the \.{WEB} change file grew to twice the size of the
original \.{DVITYPE.WEB} source, a manual \.{WEB}merge operation was
performed!\endgraf
Support for \.{\\changebar} specials was provided by Robin Fairbairns.
The code for the newer DEClaser printers is due to Karsten Nyblad.}}}
\pageno=\contentspagenumber \advance\pageno by 1
\let\maybe=\iftrue
%\let\maybe=\iffalse
\def\meta#1{{$\langle$\rm #1$\rangle$}}
\def\9#1{}% Used with @@:sort entry}{printed entry@@>
@* Introduction.
The \.{DVItoLN03} utility program reads binary device-independent (``\.{DVI}'')
files that are produced by document compilers such as \TeX, and converts them
into a form suited to driving a Digital LN03 or LN03-Plus laser printer. The
@^LN03 laser printer@>
output file (with extension \.{LN3}) contains both the instructions to typeset
@^LN3 file@>
the required characters \&{and} the commands necessary to download the
characters required (until DEC get around to providing the Computer Modern
fonts in a ROM cartridge!). The LN03-Plus will accept, without complaint,
any number of downloaded fonts: however, only the first 14 fonts (each
consisting of up to 188 characters, in the \\{GL} and \\{GR} sets) are
actually usable for imaging glyphs: attempts to image a glyph from fonts
|15..| result in the display of a character taken from one of the first 14
fonts loaded. In view of these limitations, \.{DVItoLN03} only downloads
the characters which are actually used within the document being processed;
the maximum possible number of characters, even if taken from different
\TeX\ fonts, are combined into each single `LN03 font'.
The program was developed by Brian Hamilton Kelly at the Royal Military
@^Hamilton Kelly, Brian@>
College of Science.
@^RMCS@>
@^Royal Military College of Science@>
The \.{DVI2LNG} program of Flavio Rose
@^Rose, Flavio@>
@:DVI2LNG program}{\.{DVI2LNG} program@>
was used as a starting point; this had been distributed as a change file to
be applied to the original \.{DVItype} program,
@:DVItype Program}{\.{DVItype} program@>
and ``translated'' the \.{DVI} file into two output files which were then
further processed by his \.{LN03toPP}
@:LN03toPP Program}{\.{LN03toPP} program@>
program (written in PL/I), which required device-specific versions of
unpacked pixel files. The initial step was to perform the two separate
passes within a single program (therefore the present program still needs to
make two passes through the \.{DVI} file). Later work added further
functionality including use of ``ordinary'' pixel files and support for
\TeX's \.{\BS special} command to read sixel graphics dump files and include
them verbatim within the output file. More recent revisions have produced
major versions 2, 3 and 4:
\yskip\hang\&{V2.0} Provided capability to read pixel rasters from packed
(\.{PK}) files as well as from the traditional \.{PXL} files. It also manages
to handle the glyphs of characters which are too large to load into the
LN03's memory.
\yskip\hang\&{V3.0} Changed method of accessing font files, to allow users
flexibility in the storage philosophy they adopt. Can now handle large
(256-character) fonts, such as Levy's Greek.
@^Levy, Silvio@>
@^256-character fonts@>
@:two hundred and fifty six character fonts}{256-character fonts@>
\yskip\hang\&{V3.1} Robin Fairbairns of Laser Scan (Cambridge, UK) added
@^Fairbairns, Robin@>
support for Rose's \.{\BS special}s for changebar marking at V3.1-1, June
1989.
\yskip\hang\&{V3.2} Tidied up recognition of \TeX\ \meta{physical unit}s.
Removed final vestige of `hard-wired' logical names, \\{viz:} \.{TEX\$FONTS}
@.TEX\$FONTS@>
\yskip\hang\&{V3.3} Permitted \.{/LEFT\UL MARGIN} and \.{TOP\UL MARGIN}
qualifiers to take dimensioned lengths, instead of being restricted to
pixels.
\yskip\hang\&{V3.4} Processed virtual font files, provided each character
was only mapped to a single character from some other font; did not support
virtual characters which mapped to a sequence of one or more characters from
other physical fonts, nor could it handle font \.{recurse}.
\yskip\hang\&{V3.5} Handled sequences in virtual font files; still couldn't
handle font \.{recurse}.
\yskip\hang\&{V3.6} Supports font \.{recurse}; folds multiple references to
same font (name and scale) onto single invocation.
\yskip\hang\&{V3.7} Supports landscape and portrait specials; whole page only.
\yskip\hang\&{V3.8} Supports mixture of landscape and portrait on same page.
\yskip\hang\&{V3.9} Prints glyphs from missing fonts as solid rules; added
\.{/HFUZZ} and \.{/VFUZZ} qualifiers.
\yskip\hang\&{V4.0} Supports the new DEClaser~2100 and~2200 printers (the
LN05 and LN06) in addition to the traditional LN03. With the LN06, paper
feed trays may be selected from the command line, and the duplexing
facilities controlled also. Support for these printers was provided by
Karsten Nyblad
@^Nyblad, Karsten@>
of TFL, the Danish Telecommunication Research Laboratory, and integrated
into this new release with the other features mentioned above.
One of the stated {\it raisons d'\^^Detre\/} for \.{DVItype} is ``to serve as an
example of a program that reads \.{DVI} files correctly, for system
programmers who are developing \.{DVI}-related software.'' The paradigms for
calculation provided by this example have been followed closely in the present
program (by importing them {\it verbatim\/} from \.{DVItype}!) To quote again
from \.{DVItype}:\vskip 5pt
\centerline{\hsize 5in\baselineskip9pt
\vbox{\ninerm\noindent
``Programs for typesetting need to be especially careful about how they do
arithmetic; if rounding errors accumulate, margins won't be straight,
vertical rules won't line up, and so on. But if rounding is done
everywhere, even in the midst of words, there will be uneven spacing
between the letters, and that looks bad. Human eyes notice differences
of a thousandth of an inch in the positioning of lines that are close
together; on low resolution devices, where rounding produces effects
four times as great as this, the problem is especially critical.
Experience has shown that unusual care is needed even on high-resolution
equipment; for example, a mistake in the sixth significant hexadecimal
place of a constant once led to a difficult-to-find bug in some software
for the Alphatype CRS, which has a resolution of 5333 pixels per inch
(make that 5333.33333333 pixels per inch). The document compilers that
generate \.{DVI} files make certain assumptions about the arithmetic
that will be used by \.{DVI}-reading software, and if these assumptions
are violated the results will be of inferior quality.''
}}
The first \.{DVItype} program was designed by David Fuchs in 1979, and it
@^Fuchs, David Raymond@>
went through several versions on different computers as the format of
\.{DVI} files was evolving to its present form.
The \.{LN3} output file produced by DVItoLN03 always commences by including
commands to down-load one or more font-files (each of a maximum of 188
characters) to define just those characters used in the document. The rest of
the file consists of commands to set those characters where required. Because
the mapping of source characters to those in the down-loaded fonts is
arbitrary, the output file is unintelligible to human readers!
Whilst many of the earlier changes tried to preserve the
``machine-independence'' of the program, no attempt has been made in this
version, since it is assumed that the LN03 printer will be used on a VAX
computer. In particular, |random_reading| is used on some of the input files,
and is necessary.
The |banner| string defined here should be changed whenever \.{DVItoLN03}
gets modified.
@d banner=='This is DVItoLN03, Vax/VMS Version 4.0'
@ This program is written mostly in standard \PASCAL, except where it is
necessary to use extensions; for example, \.{DVItoLN03} must read files whose
names are dynamically specified, and that would be impossible in pure \PASCAL.
Many features of VAX-\PASCAL\ have been incorporated, and no attempt has been
made to ensure that all places where such nonstandard constructions are used
have been listed in the index under ``system dependencies.''
@!@^system dependencies@>
One of the extensions to standard \PASCAL\ that we shall deal with is the
ability to move to a random place in a binary file; another is to
determine the length of a binary file. Such extensions are not necessary
for reading \.{DVI} files, and they are not important for efficiency
reasons either. However, when creating the `font files' for
downloading the necessary characters to the LN03, \.{DVItoLN03} \&{has} to be
able to perform |random_reading|, to locate the glyph of each character within
the pixel file. Therefore, if \.{DVItoLN03} is being used with
\PASCAL s for which random file positioning is not efficiently available,
and the following definition is changed from |true| to |false|, further work
will still be required to re-code the processing of the pixel files.
Another extension is to use a default |case| as in \.{TANGLE}, \.{WEAVE},
etc.
@d random_reading==true {should we skip around in the file?}
@d othercases == otherwise {Vax/VMS default for cases not listed explicitly}
@d endcases == @+end {follows the default case in an extended |case| statement}
@f othercases == else
@f endcases == end
@ The binary input comes from |dvi_file|, and the \.{LN3} file is written to
|ln3_file|. \.{DVItoLN03} also produces a `log' file using |type_file|.
Errors and some informational messages are written to this, and are also
written to \PASCAL's standard |output| file. The term |print| is used instead
of |write| when this program writes on |type_file|, so that all such output
could easily be redirected if desired.
Under Vax/VMS we use double-precision for all real values. Furthermore, since
extensive reference is made to Vax/VMS system services and other library
routines, we arrange for the \PASCAL\ program to `inherit' all their
definitions (from \.{STARLET}).
@d print(#)==write(type_file,#)
@d print_ln(#)==write_ln(type_file,#)
@d real==double
@p @=[INHERIT ('SYS$LIBRARY:STARLET')]@> program DVI_to_LN03(@!dvi_file,
@!tfm_file,@!type_file,@!output,@!ln3_file);
label @<Labels in the outer block@>@/
const @<Constants in the outer block@>@/
type @<Types in the outer block@>@/
var @<Globals in the outer block@>@/
procedure jump_out; forward;@#
@t\2@>@<Procedures for initialization@>@;@#
procedure initialize; {this procedure gets things started properly}
var @!i:integer; {loop index for initializations}
begin
@<Preset initial values@>@/
print_ln(banner);@/
@<Set initial values@>@/
end;@#
@<Additional low-level procedures@>@;
@ If the program has to stop prematurely, it goes to the
`|final_end|'. Another label, |done|, is used when stopping normally.
@d final_end=9999 {label for the end of it all}
@d done=30 {go here when finished with a subtask}
@<Labels...@>=final_end,done;
@ The following parameters can be changed at compile time to extend or
reduce \.{DVItoLN03}'s capacity.
As previously mentioned, the LN03-plus is quite happy to accept any number
of downloaded fonts, but is incapable of imaging glyphs from more than
fourteen of them! (The basic LN03 won't accept more than 21 half-fonts.)
Each such font has to be selected by mapping it to one of 10 different
Select Graphic Renditions; this is reflected by the constant |max_SGR|.
Whenever bitmaps are sent to the printer (in sixel format), it is necessary
to establish the ``printing'' position 29 pixels above the current
reference point. Despite repeated enquiry of DEC, it has not been
established why this is necessary. Naturally, we call this 29 pixels
|bitmap_offset|. This same offset also has to be applied to sixel graphics
included through a \.{\\special} command.
When the paper orientation is selected, we may define the |page_len| and
|page_wid| in terms of the physical limits of the paper; these sizes are
as follows (for European A4 paper).
@^European A4 paper@>
(These might later become variables, and be alterable by qualifiers in the
command line.) There is, however, a further complication. In either printing
orientation, the printable area is of these dimensions; however, this printing
area starts 0.25~inches from the top left corner of the paper \\{in the
orientation it enters the printer}. Therefore in portrait mode, we can
actually print from (75,75) to (2475,3475), with (300,300) being the
``normal'' \TeX\ origin, but in landscape mode we can print (35,75) to
(3435,2475), and \TeX's origin should still be at (300,300).
The LN03 has a physical (apparently arbitrary) restriction on the maximum size
of a downloadable glyph. For a glyph to be acceptable, it must satisfy the
inequality $$
2\times\lceil|num_cols|/2\rceil\times\lceil|num_rows|/8\rceil\le5700$$
The type |file_spec| is used for declaring data objects containing VAX/RMS
file specifications, and for declaring procedures which handle them.
@d max_line=200 {Maximum length of any line output to the LN03}
@d max_print=175 {Maximum number of characters imaged on line}
@#
@d max_lnfonts=13 {Limitations of the LN03's hardware}
@d max_SGR=9
@#
@d max_special=300 {maximum size of a \.{\\special} command that we can deal
with}
@d max_points=256 {number of points for the \.{\\special} rule commands}
@d max_strings=30 {Distinct string parameters of \.{\\special}s}
@d pool_size=100 {Total characters in those strings}
@#
@d bitmap_offset=29
@#
@d paper_ht=3400 {in pixels}
@d paper_wid=2400
@d y_port_min=75
@d y_land_min=35
@d page_x_min=75
@d y_top=75 {Minimum addressable $y$ pixel}
@d y_bot=paper_ht+y_port_min {Maximum addressable $y$ pixel}
@#
@d PFS_landscape=='?23 J' {Extended A4 paper in landscape orientation}
@d PFS_portrait=='?22 J' {ditto in portrait; each preceded by |csi|}
@#
@d lnf_bufsize=2000 {Maximum block size of an LN03 ``font file'' in memory ($\approx1$MB)}
@#
@d largest_glyph=5700
@#
@d max_fonts=100 {maximum number of distinct fonts per \.{DVI} file}
@d max_glyphs=10000 {maximum number of different characters among all fonts}
@#
@d line_length=79 {bracketed lines of output will be at most this long}
@d terminal_line_length=150 {maximum number of characters input in a single
line of input from the command line interpreter}
@#
@d stack_size=100 {\.{DVI} files shouldn't |push| beyond this depth}
@d name_size=1000 {total length of all font file names}
@#
@d name_length=255 {a file name shouldn't be longer than this}
@#
@d max_rules=16384 {maximum number of rules per page}
@d max_lap_rules=3 {maximum under/over lap of rules}
@#
@d vf_size=10000 {maximum |dvi| commands stored from virtual font files}
@#
@d max_blank_pages=100 {blank \\{verso} pages between successive \\{recto}s}
@<Types...@>=
@!file_spec = varying [name_length] of char;
@ Here are some macros for common programming idioms.
@d incr(#) == #:=#+1 {increase a variable by unity}
@d decr(#) == #:=#-1 {decrease a variable by unity}
@d do_nothing == {empty statement}
@ If the \.{DVI} file is badly malformed, the whole process must be aborted;
\.{DVItoLN03} will give up, after issuing an error message about the symptoms
that were noticed.
Such errors might be discovered inside of subroutines inside of subroutines,
so a procedure called |jump_out| has been introduced. This procedure, which
simply transfers control to the label |final_end| at the end of the program,
contains the only non-local |goto| statement in \.{DVItoLN03}.
@^system dependencies@>
@d crlf==chr(13)+chr(10)
@d abort(#)==begin
history := fatal_error; print_ln(' ');
print_ln(' Fatal error: ',#);
@!@.Fatal error ...@>
write_ln(output,crlf,'Fatal error: ',#,crlf);
jump_out;
end
@d bad_dvi(#)==abort('Bad DVI file: ',#,'!')
@!@.Bad DVI file@>
@!@:fatal error Bad DVI file}{\quad\.{Bad DVI file} \\{(q.v.)}@>
@d capacity_exceeded(#)==abort('capacity exceeded [',#,']')
@!@.capacity exceeded@>
@!@:fatal error capacity exceeded}{\quad\.{capacity exceeded} \\{(q.v.)}@>
@p procedure jump_out;
begin goto final_end;
end;
@* The character set.
Like all programs written with the \.{WEB} system, \.{DVItoLN03} can be
used with any character set. But it uses ASCII code internally, because
the programming for portable input-output is easier when a fixed internal
code is used, and because \.{DVI} files use ASCII code for file names
and certain other strings.
The next few sections of \.{DVItoLN03} have therefore been copied from the
analogous ones in the \.{WEB} system routines. They have been considerably
simplified, since \.{DVItoLN03} need not deal with the controversial
ASCII codes less than @'40. If such codes appear in the \.{DVI} file,
they will be printed as question marks.
All characters still occupy 8 bits of storage, to avoid overhead with |packed
array|s.
@<Types...@>=
@!ASCII_code= @=[byte]@> " ".."~"; {a subrange of the integers}
@ The original \PASCAL\ compiler was designed in the late 60s, when six-bit
character sets were common, so it did not make provision for lower case
letters. Nowadays, of course, we need to deal with both upper and lower case
alphabets in a convenient way, especially in a program like \.{DVItoLN03}.
So we shall assume that the \PASCAL\ system being used for \.{DVItoLN03}
has a character set containing at least the standard visible characters
of ASCII code (|"!"| through |"~"|).
Some \PASCAL\ compilers use the original name |char| for the data type
associated with the characters in text files, while other \PASCAL s
consider |char| to be a 64-element subrange of a larger data type that has
some other name. In order to accommodate this difference, we shall use
the name |text_char| to stand for the data type of the characters in the
output file. We shall also assume that |text_char| consists of
the elements |chr(first_text_char)| through |chr(last_text_char)|,
inclusive. The following definitions should be adjusted if necessary.
@^system dependencies@>
@d text_char == char {the data type of characters in text files}
@d first_text_char=0 {ordinal number of the smallest element of |text_char|}
@d last_text_char=255 {ordinal number of the largest element of |text_char|}
@<Types...@>=
@!text_file=packed file of text_char;
@ The \.{DVItoLN03} processor converts between ASCII code and
the user's external character set by means of arrays |xord| and |xchr|
that are analogous to \PASCAL's |ord| and |chr| functions.
@<Globals...@>=
@!xord: array [text_char] of ASCII_code;
{specifies conversion of input characters}
@!xchr: array [first_text_char..last_text_char] of text_char;
{specifies conversion of output characters}
@ Under our assumption that the visible characters of standard ASCII are
all present, the following assignment statements initialize the
|xchr| array properly, without needing any system-dependent changes.
@<Set init...@>=
for i:=0 to @'37 do xchr[i]:='?';
xchr[@'40]:=' ';
xchr[@'41]:='!';
xchr[@'42]:='"';
xchr[@'43]:='#';
xchr[@'44]:='$';
xchr[@'45]:='%';
xchr[@'46]:='&';
xchr[@'47]:='''';@/
xchr[@'50]:='(';
xchr[@'51]:=')';
xchr[@'52]:='*';
xchr[@'53]:='+';
xchr[@'54]:=',';
xchr[@'55]:='-';
xchr[@'56]:='.';
xchr[@'57]:='/';@/
xchr[@'60]:='0';
xchr[@'61]:='1';
xchr[@'62]:='2';
xchr[@'63]:='3';
xchr[@'64]:='4';
xchr[@'65]:='5';
xchr[@'66]:='6';
xchr[@'67]:='7';@/
xchr[@'70]:='8';
xchr[@'71]:='9';
xchr[@'72]:=':';
xchr[@'73]:=';';
xchr[@'74]:='<';
xchr[@'75]:='=';
xchr[@'76]:='>';
xchr[@'77]:='?';@/
xchr[@'100]:='@@';
xchr[@'101]:='A';
xchr[@'102]:='B';
xchr[@'103]:='C';
xchr[@'104]:='D';
xchr[@'105]:='E';
xchr[@'106]:='F';
xchr[@'107]:='G';@/
xchr[@'110]:='H';
xchr[@'111]:='I';
xchr[@'112]:='J';
xchr[@'113]:='K';
xchr[@'114]:='L';
xchr[@'115]:='M';
xchr[@'116]:='N';
xchr[@'117]:='O';@/
xchr[@'120]:='P';
xchr[@'121]:='Q';
xchr[@'122]:='R';
xchr[@'123]:='S';
xchr[@'124]:='T';
xchr[@'125]:='U';
xchr[@'126]:='V';
xchr[@'127]:='W';@/
xchr[@'130]:='X';
xchr[@'131]:='Y';
xchr[@'132]:='Z';
xchr[@'133]:='[';
xchr[@'134]:='\';
xchr[@'135]:=']';
xchr[@'136]:='^';
xchr[@'137]:='_';@/
xchr[@'140]:='`';
xchr[@'141]:='a';
xchr[@'142]:='b';
xchr[@'143]:='c';
xchr[@'144]:='d';
xchr[@'145]:='e';
xchr[@'146]:='f';
xchr[@'147]:='g';@/
xchr[@'150]:='h';
xchr[@'151]:='i';
xchr[@'152]:='j';
xchr[@'153]:='k';
xchr[@'154]:='l';
xchr[@'155]:='m';
xchr[@'156]:='n';
xchr[@'157]:='o';@/
xchr[@'160]:='p';
xchr[@'161]:='q';
xchr[@'162]:='r';
xchr[@'163]:='s';
xchr[@'164]:='t';
xchr[@'165]:='u';
xchr[@'166]:='v';
xchr[@'167]:='w';@/
xchr[@'170]:='x';
xchr[@'171]:='y';
xchr[@'172]:='z';
xchr[@'173]:='{';
xchr[@'174]:='|';
xchr[@'175]:='}';
xchr[@'176]:='~';
for i:=@'177 to 255 do xchr[i]:='?';
@ The following system-independent code makes the |xord| array contain a
suitable inverse to the information in |xchr|.
@<Set init...@>=
for i:=first_text_char to last_text_char do xord[chr(i)]:=@'40;
for i:=" " to "~" do xord[xchr[i]]:=i;
@* Device-independent file format.
Before we get into the details of \.{DVItoLN03}, we need to know exactly
what \.{DVI} files are. The form of such files was designed by David R.
@^Fuchs, David Raymond@>
Fuchs in 1979. Almost any reasonable typesetting device can be driven by
a program that takes \.{DVI} files as input, and dozens of such
\.{DVI}-to-whatever programs have been written. Thus, it is possible to
print the output of document compilers like \TeX\ on many different kinds
of equipment.
A \.{DVI} file is a stream of 8-bit bytes, which may be regarded as a
series of commands in a machine-like language. The first byte of each command
is the operation code, and this code is followed by zero or more bytes
that provide parameters to the command. The parameters themselves may consist
of several consecutive bytes; for example, the `|set_rule|' command has two
parameters, each of which is four bytes long. Parameters are usually
regarded as nonnegative integers; but four-byte-long parameters,
and shorter parameters that denote distances, can be
either positive or negative. Such parameters are given in two's complement
notation. For example, a two-byte-long distance parameter has a value between
$-2^{15}$ and $2^{15}-1$.
@.DVI {\rm files}@>
A \.{DVI} file consists of a ``preamble,'' followed by a sequence of one
or more ``pages,'' followed by a ``postamble.'' The preamble is simply a
|pre| command, with its parameters that define the dimensions used in the
file; this must come first. Each ``page'' consists of a |bop| command,
followed by any number of other commands that tell where characters are to
be placed on a physical page, followed by an |eop| command. The pages
appear in the order that they were generated, not in any particular
numerical order. If we ignore |nop| commands and \\{fnt\_def} commands
(which are allowed between any two commands in the file), each |eop|
command is immediately followed by a |bop| command, or by a |post|
command; in the latter case, there are no more pages in the file, and the
remaining bytes form the postamble. Further details about the postamble
will be explained later.
Some parameters in \.{DVI} commands are ``pointers.'' These are four-byte
quantities that give the location number of some other byte in the file;
the first byte is number~0, then comes number~1, and so on. For example,
one of the parameters of a |bop| command points to the previous |bop|;
this makes it feasible to read the pages in backwards order, in case the
results are being directed to a device that stacks its output face up.
Suppose the preamble of a \.{DVI} file occupies bytes 0 to 99. Now if the
first page occupies bytes 100 to 999, say, and if the second
page occupies bytes 1000 to 1999, then the |bop| that starts in byte 1000
points to 100 and the |bop| that starts in byte 2000 points to 1000. (The
very first |bop|, i.e., the one that starts in byte 100, has a pointer of $-1$.)
@<Glob...@>=
@!dvi_file:byte_file; {the stuff we are \.{DVItoLN03}ing}
@ The \.{DVI} format is intended to be both compact and easily interpreted
by a machine. Compactness is achieved by making most of the information
implicit instead of explicit. When a \.{DVI}-reading program reads the
commands for a page, it keeps track of several quantities: (a)~The current
font |f| is an integer; this value is changed only
by \\{fnt} and \\{fnt\_num} commands. (b)~The current position on the page
is given by two numbers called the horizontal and vertical coordinates,
|h| and |v|. Both coordinates are zero at the upper left corner of the page;
moving to the right corresponds to increasing the horizontal coordinate, and
moving down corresponds to increasing the vertical coordinate. Thus, the
coordinates are essentially Cartesian, except that vertical directions are
flipped; the Cartesian version of |(h,v)| would be |(h,-v)|. (c)~The
current spacing amounts are given by four numbers |w|, |x|, |y|, and |z|,
where |w| and~|x| are used for horizontal spacing and where |y| and~|z|
are used for vertical spacing. (d)~There is a stack containing
|(h,v,w,x,y,z)| values; the \.{DVI} commands |push| and |pop| are used to
change the current level of operation. Note that the current font~|f| is
not pushed and popped; the stack contains only information about
positioning.
The values of |h|, |v|, |w|, |x|, |y|, and |z| are signed integers having up
to 32 bits, including the sign. Since they represent physical distances,
there is a small unit of measurement such that increasing |h| by~1 means
moving a certain tiny distance to the right. The actual unit of
measurement is variable, as explained below.
@ Here is a list of all the commands that may appear in a \.{DVI} file. Each
command is specified by its symbolic name (e.g., |bop|), its opcode byte
(e.g., 139), and its parameters (if any). The parameters are followed
by a bracketed number telling how many bytes they occupy; for example,
`|p[4]|' means that parameter |p| is four bytes long.
\yskip\hang|set_char_0| 0. Typeset character number~0 from font~|f|
such that the reference point of the character is at |(h,v)|. Then
increase |h| by the width of that character. Note that a character may
have zero or negative width, so one cannot be sure that |h| will advance
after this command; but |h| usually does increase.
\yskip\hang|set_char_1| through |set_char_127| (opcodes 1 to 127).
Do the operations of |set_char_0|; but use the character whose number
matches the opcode, instead of character~0.
\yskip\hang|set1| 128 |c[1]|. Same as |set_char_0|, except that character
number~|c| is typeset. \TeX82 uses this command for characters in the
range |128<=c<256|.
\yskip\hang|set2| 129 |c[2]|. Same as |set1|, except that |c|~is two
bytes long, so it is in the range |0<=c<65536|. \TeX82 never uses this
command, which is intended for processors that deal with oriental languages;
but \.{DVItoLN03} will allow character codes greater than 255, assuming that
they all have the same width as the character whose code is $c \bmod 256$.
@^oriental characters@>@^Chinese characters@>@^Japanese characters@>
\yskip\hang|set3| 130 |c[3]|. Same as |set1|, except that |c|~is three
bytes long, so it can be as large as $2^{24}-1$.
\yskip\hang|set4| 131 |c[4]|. Same as |set1|, except that |c|~is four
bytes long, possibly even negative. Imagine that.
\yskip\hang|set_rule| 132 |a[4]| |b[4]|. Typeset a solid black rectangle
of height |a| and width |b|, with its bottom left corner at |(h,v)|. Then
set |h:=h+b|. If either |a<=0| or |b<=0|, nothing should be typeset. Note
that if |b<0|, the value of |h| will decrease even though nothing else happens.
Programs that typeset from \.{DVI} files should be careful to make the rules
line up carefully with digitized characters, as explained in connection with
the |rule_pixels| subroutine below.
\yskip\hang|put1| 133 |c[1]|. Typeset character number~|c| from font~|f|
such that the reference point of the character is at |(h,v)|. (The `put'
commands are exactly like the `set' commands, except that they simply put out a
character or a rule without moving the reference point afterwards.)
\yskip\hang|put2| 134 |c[2]|. Same as |set2|, except that |h| is not changed.
\yskip\hang|put3| 135 |c[3]|. Same as |set3|, except that |h| is not changed.
\yskip\hang|put4| 136 |c[4]|. Same as |set4|, except that |h| is not changed.
\yskip\hang|put_rule| 137 |a[4]| |b[4]|. Same as |set_rule|, except that
|h| is not changed.
\yskip\hang|nop| 138. No operation, do nothing. Any number of |nop|'s
may occur between \.{DVI} commands, but a |nop| cannot be inserted between
a command and its parameters or between two parameters.
\yskip\hang|bop| 139 $c_0[4]$ $c_1[4]$ $\ldots$ $c_9[4]$ $p[4]$. Beginning
of a page: Set |(h,v,w,x,y,z):=(0,0,0,0,0,0)| and set the stack empty. Set
the current font |f| to an undefined value. The ten $c_i$ parameters can
be used to identify pages, if a user wants to print only part of a \.{DVI}
file; \TeX82 gives them the values of \.{\\count0} $\ldots$ \.{\\count9}
at the time \.{\\shipout} was invoked for this page. The parameter |p|
points to the previous |bop| command in the file, where the first |bop|
has $p=-1$.
\yskip\hang|eop| 140. End of page: Print what you have read since the
previous |bop|. At this point the stack should be empty. (The \.{DVI}-reading
programs that drive most output devices will have kept a buffer of the
material that appears on the page that has just ended. This material is
largely, but not entirely, in order by |v| coordinate and (for fixed |v|) by
|h|~coordinate; so it usually needs to be sorted into some order that is
appropriate for the device in question. \.{DVItoLN03} does do such sorting.)
\yskip\hang|push| 141. Push the current values of |(h,v,w,x,y,z)| onto the
top of the stack; do not change any of these values. Note that |f| is
not pushed.
\yskip\hang|pop| 142. Pop the top six values off of the stack and assign
them to |(h,v,w,x,y,z)|. The number of pops should never exceed the number
of pushes, since it would be highly embarrassing if the stack were empty
at the time of a |pop| command.
\yskip\hang|right1| 143 |b[1]|. Set |h:=h+b|, i.e., move right |b| units.
The parameter is a signed number in two's complement notation, |-128<=b<128|;
if |b<0|, the reference point actually moves left.
\yskip\hang|right2| 144 |b[2]|. Same as |right1|, except that |b| is a
two-byte quantity in the range |-32768<=b<32768|.
\yskip\hang|right3| 145 |b[3]|. Same as |right1|, except that |b| is a
three-byte quantity in the range |@t$-2^{23}$@><=b<@t$2^{23}$@>|.
\yskip\hang|right4| 146 |b[4]|. Same as |right1|, except that |b| is a
four-byte quantity in the range |@t$-2^{31}$@><=b<@t$2^{31}$@>|.
\yskip\hang|w0| 147. Set |h:=h+w|; i.e., move right |w| units. With luck,
this parameterless command will usually suffice, because the same kind of motion
will occur several times in succession; the following commands explain how
|w| gets particular values.
\yskip\hang|w1| 148 |b[1]|. Set |w:=b| and |h:=h+b|. The value of |b| is a
signed quantity in two's complement notation, |-128<=b<128|. This command
changes the current |w|~spacing and moves right by |b|.
\yskip\hang|w2| 149 |b[2]|. Same as |w1|, but |b| is a two-byte-long
parameter, |-32768<=b<32768|.
\yskip\hang|w3| 150 |b[3]|. Same as |w1|, but |b| is a three-byte-long
parameter, |@t$-2^{23}$@><=b<@t$2^{23}$@>|.
\yskip\hang|w4| 151 |b[4]|. Same as |w1|, but |b| is a four-byte-long
parameter, |@t$-2^{31}$@><=b<@t$2^{31}$@>|.
\yskip\hang|x0| 152. Set |h:=h+x|; i.e., move right |x| units. The `|x|'
commands are like the `|w|' commands except that they involve |x| instead
of |w|.
\yskip\hang|x1| 153 |b[1]|. Set |x:=b| and |h:=h+b|. The value of |b| is a
signed quantity in two's complement notation, |-128<=b<128|. This command
changes the current |x|~spacing and moves right by |b|.
\yskip\hang|x2| 154 |b[2]|. Same as |x1|, but |b| is a two-byte-long
parameter, |-32768<=b<32768|.
\yskip\hang|x3| 155 |b[3]|. Same as |x1|, but |b| is a three-byte-long
parameter, |@t$-2^{23}$@><=b<@t$2^{23}$@>|.
\yskip\hang|x4| 156 |b[4]|. Same as |x1|, but |b| is a four-byte-long
parameter, |@t$-2^{31}$@><=b<@t$2^{31}$@>|.
\yskip\hang|down1| 157 |a[1]|. Set |v:=v+a|, i.e., move down |a| units.
The parameter is a signed number in two's complement notation, |-128<=a<128|;
if |a<0|, the reference point actually moves up.
\yskip\hang|down2| 158 |a[2]|. Same as |down1|, except that |a| is a
two-byte quantity in the range |-32768<=a<32768|.
\yskip\hang|down3| 159 |a[3]|. Same as |down1|, except that |a| is a
three-byte quantity in the range |@t$-2^{23}$@><=a<@t$2^{23}$@>|.
\yskip\hang|down4| 160 |a[4]|. Same as |down1|, except that |a| is a
four-byte quantity in the range |@t$-2^{31}$@><=a<@t$2^{31}$@>|.
\yskip\hang|y0| 161. Set |v:=v+y|; i.e., move down |y| units. With luck,
this parameterless command will usually suffice, because the same kind of motion
will occur several times in succession; the following commands explain how
|y| gets particular values.
\yskip\hang|y1| 162 |a[1]|. Set |y:=a| and |v:=v+a|. The value of |a| is a
signed quantity in two's complement notation, |-128<=a<128|. This command
changes the current |y|~spacing and moves down by |a|.
\yskip\hang|y2| 163 |a[2]|. Same as |y1|, but |a| is a two-byte-long
parameter, |-32768<=a<32768|.
\yskip\hang|y3| 164 |a[3]|. Same as |y1|, but |a| is a three-byte-long
parameter, |@t$-2^{23}$@><=a<@t$2^{23}$@>|.
\yskip\hang|y4| 165 |a[4]|. Same as |y1|, but |a| is a four-byte-long
parameter, |@t$-2^{31}$@><=a<@t$2^{31}$@>|.
\yskip\hang|z0| 166. Set |v:=v+z|; i.e., move down |z| units. The `|z|' commands
are like the `|y|' commands except that they involve |z| instead of |y|.
\yskip\hang|z1| 167 |a[1]|. Set |z:=a| and |v:=v+a|. The value of |a| is a
signed quantity in two's complement notation, |-128<=a<128|. This command
changes the current |z|~spacing and moves down by |a|.
\yskip\hang|z2| 168 |a[2]|. Same as |z1|, but |a| is a two-byte-long
parameter, |-32768<=a<32768|.
\yskip\hang|z3| 169 |a[3]|. Same as |z1|, but |a| is a three-byte-long
parameter, |@t$-2^{23}$@><=a<@t$2^{23}$@>|.
\yskip\hang|z4| 170 |a[4]|. Same as |z1|, but |a| is a four-byte-long
parameter, |@t$-2^{31}$@><=a<@t$2^{31}$@>|.
\yskip\hang|fnt_num_0| 171. Set |f:=0|. Font 0 must previously have been
defined by a \\{fnt\_def} instruction, as explained below.
\yskip\hang|fnt_num_1| through |fnt_num_63| (opcodes 172 to 234). Set
|f:=1|, \dots, |f:=63|, respectively.
\yskip\hang|fnt1| 235 |k[1]|. Set |f:=k|. \TeX82 uses this command for font
numbers in the range |64<=k<256|.
\yskip\hang|fnt2| 236 |k[2]|. Same as |fnt1|, except that |k|~is two
bytes long, so it is in the range |0<=k<65536|. \TeX82 never generates this
command, but large font numbers may prove useful for specifications of
color or texture, or they may be used for special fonts that have fixed
numbers in some external coding scheme.
\yskip\hang|fnt3| 237 |k[3]|. Same as |fnt1|, except that |k|~is three
bytes long, so it can be as large as $2^{24}-1$.
\yskip\hang|fnt4| 238 |k[4]|. Same as |fnt1|, except that |k|~is four
bytes long; this is for the really big font numbers (and for the negative ones).
\yskip\hang|xxx1| 239 |k[1]| |x[k]|. This command is undefined in
general; it functions as a $(k+2)$-byte |nop| unless special \.{DVI}-reading
programs are being used. \TeX82 generates |xxx1| when a short enough
\.{\\special} appears, setting |k| to the number of bytes being sent. It
is recommended that |x| be a string having the form of a keyword followed
by possible parameters relevant to that keyword.
\yskip\hang|xxx2| 240 |k[2]| |x[k]|. Like |xxx1|, but |0<=k<65536|.
\yskip\hang|xxx3| 241 |k[3]| |x[k]|. Like |xxx1|, but |0<=k<@t$2^{24}$@>|.
\yskip\hang|xxx4| 242 |k[4]| |x[k]|. Like |xxx1|, but |k| can be ridiculously
large. \TeX82 uses |xxx4| when |xxx1| would be incorrect.
\yskip\hang|fnt_def1| 243 |k[1]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|.
Define font |k|, where |0<=k<256|; font definitions will be explained shortly.
\yskip\hang|fnt_def2| 244 |k[2]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|.
Define font |k|, where |0<=k<65536|.
\yskip\hang|fnt_def3| 245 |k[3]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|.
Define font |k|, where |0<=k<@t$2^{24}$@>|.
\yskip\hang|fnt_def4| 246 |k[4]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|.
Define font |k|, where |@t$-2^{31}$@><=k<@t$2^{31}$@>|.
\yskip\hang|pre| 247 |i[1]| |num[4]| |den[4]| |mag[4]| |k[1]| |x[k]|.
Beginning of the preamble; this must come at the very beginning of the
file. Parameters |i|, |num|, |den|, |mag|, |k|, and |x| are explained below.
\yskip\hang|post| 248. Beginning of the postamble, see below.
\yskip\hang|post_post| 249. Ending of the postamble, see below.
\yskip\noindent Commands 250--255 are undefined at the present time.
@ @d set_char_0=0 {typeset character 0 and move right}
@d set1=128 {typeset a character and move right}
@d set_rule=132 {typeset a rule and move right}
@d put1=133 {typeset a character}
@d put_rule=137 {typeset a rule}
@d nop=138 {no operation}
@d bop=139 {beginning of page}
@d eop=140 {ending of page}
@d push=141 {save the current positions}
@d pop=142 {restore previous positions}
@d right1=143 {move right}
@d w0=147 {move right by |w|}
@d w1=148 {move right and set |w|}
@d x0=152 {move right by |x|}
@d x1=153 {move right and set |x|}
@d down1=157 {move down}
@d y0=161 {move down by |y|}
@d y1=162 {move down and set |y|}
@d z0=166 {move down by |z|}
@d z1=167 {move down and set |z|}
@d fnt_num_0=171 {set current font to 0}
@d fnt1=235 {set current font}
@d xxx1=239 {extension to \.{DVI} primitives}
@d xxx4=242 {potentially long extension to \.{DVI} primitives}
@d fnt_def1=243 {define the meaning of a font number}
@d pre=247 {preamble}
@d post=248 {postamble beginning}
@d post_post=249 {postamble ending}
@d undefined_commands==250,251,252,253,254,255
@ The preamble contains basic information about the file as a whole. As
stated above, there are six parameters:
$$\hbox{|@!i[1]| |@!num[4]| |@!den[4]| |@!mag[4]| |@!k[1]| |@!x[k]|.}$$
The |i| byte identifies \.{DVI} format; currently this byte is always set
to~2. (Some day we will set |i=3|, when \.{DVI} format makes another
incompatible change---perhaps in 1992.)
The next two parameters, |num| and |den|, are positive integers that define
the units of measurement; they are the numerator and denominator of a
fraction by which all dimensions in the \.{DVI} file could be multiplied
in order to get lengths in units of $10^{-7}$ meters. (For example, there are
exactly 7227 \TeX\ points in 254 centimeters, and \TeX82 works with scaled
points where there are $2^{16}$ sp in a point, so \TeX82 sets |num=25400000|
and $|den|=7227\cdot2^{16}=473628672$.)
@^sp@>
The |mag| parameter is what \TeX82 calls \.{\\mag}, i.e., 1000 times the
desired magnification. The actual fraction by which dimensions are
multiplied is therefore $mn/1000d$. Note that if a \TeX\ source document
does not call for any `\.{true}' dimensions, and if you change it only by
specifying a different \.{\\mag} setting, the \.{DVI} file that \TeX\
creates will be completely unchanged except for the value of |mag| in the
preamble and postamble. (Fancy \.{DVI}-reading programs allow users to
override the |mag|~setting when a \.{DVI} file is being printed; this facility
has not been implemented in \.{DVItoLN03}.)
Finally, |k| and |x| allow the \.{DVI} writer to include a comment, which is not
interpreted further. The length of comment |x| is |k|, where |0<=k<256|.
@d id_byte=2 {identifies the kind of \.{DVI} files described here}
@ Font definitions for a given font number |k| contain further parameters
$$\hbox{|c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|.}$$
The four-byte value |c| is the check sum that \TeX\ (or whatever program
generated the \.{DVI} file) found in the \.{TFM} file for this font;
|c| should match the check sum of the font found by programs that read
this \.{DVI} file.
@^check sum@>
Parameter |s| contains a fixed-point scale factor that is applied to the
character widths in font |k|; font dimensions in \.{TFM} files and other
font files are relative to this quantity, which is always positive and
less than $2^{27}$. It is given in the same units as the other dimensions
of the \.{DVI} file. Parameter |d| is similar to |s|; it is the ``design
size,'' and it is given in \.{DVI} units that have not been corrected for
the magnification~|mag| found in the preamble. Thus, font |k| is to be
used at $|mag|\cdot s/1000d$ times its normal size.
The remaining part of a font definition gives the external name of the font,
which is an ASCII string of length |a+l|. The number |a| is the length
of the ``area'' or directory, and |l| is the length of the font name itself;
the standard local system font area is supposed to be used when |a=0|.
The |n| field contains the area in its first |a| bytes.
Font definitions must appear before the first use of a particular font number.
Once font |k| is defined, it must not be defined again; however, we
shall see below that font definitions appear in the postamble as well as
in the pages, so in this sense each font number is defined exactly twice,
if at all. Like |nop| commands and \\{xxx} commands, font definitions can
appear before the first |bop|, or between an |eop| and a |bop|.
@ The last page in a \.{DVI} file is followed by `|post|'; this command
introduces the postamble, which summarizes important facts that \TeX\ has
accumulated about the file, making it possible to print subsets of the data
with reasonable efficiency. The postamble has the form
$$\vbox{\halign{\hbox{#\hfil}\cr
|post| |p[4]| |num[4]| |den[4]| |mag[4]| |l[4]| |u[4]| |s[2]| |t[2]|\cr
$\langle\,$font definitions$\,\rangle$\cr
|post_post| |q[4]| |i[1]| 223's$[{\G}4]$\cr}}$$
Here |p| is a pointer to the final |bop| in the file. The next three
parameters, |num|, |den|, and |mag|, are duplicates of the quantities that
appeared in the preamble.
Parameters |l| and |u| give respectively the height-plus-depth of the tallest
page and the width of the widest page, in the same units as other dimensions
of the file. These numbers might be used by a \.{DVI}-reading program to
position individual ``pages'' on large sheets of film or paper; however,
the standard convention for output on normal size paper is to position each
page so that the upper left-hand corner is exactly one inch from the left
and the top. Experience has shown that it is unwise to design \.{DVI}-to-printer
software that attempts cleverly to center the output; a fixed position of
the upper left corner is easiest for users to understand and to work with.
Therefore |l| and~|u| are often ignored.
Parameter |s| is the maximum stack depth (i.e., the largest excess of
|push| commands over |pop| commands) needed to process this file. Then
comes |t|, the total number of pages (|bop| commands) present.
The postamble continues with font definitions, which are any number of
\\{fnt\_def} commands as described above, possibly interspersed with |nop|
commands. Each font number that is used in the \.{DVI} file must be defined
exactly twice: Once before it is first selected by a \\{fnt} command, and once
in the postamble.
@ The last part of the postamble, following the |post_post| byte that
signifies the end of the font definitions, contains |q|, a pointer to the
|post| command that started the postamble. An identification byte, |i|,
comes next; this currently equals~2, as in the preamble.
The |i| byte is followed by four or more bytes that are all equal to
the decimal number 223 (i.e., @'337 in octal). \TeX\ puts out four to seven of
these trailing bytes, until the total length of the file is a multiple of
four bytes, since this works out best on machines that pack four bytes per
word; but any number of 223's is allowed, as long as there are at least four
of them. In effect, 223 is a sort of signature that is added at the very end.
@^Fuchs, David Raymond@>
This curious way to finish off a \.{DVI} file makes it feasible for
\.{DVI}-reading programs to find the postamble first, on most computers,
even though \TeX\ wants to write the postamble last. Most operating
systems permit random access to individual words or bytes of a file, so
the \.{DVI} reader can start at the end and skip backwards over the 223's
until finding the identification byte. Then it can back up four bytes, read
|q|, and move to byte |q| of the file. This byte should, of course,
contain the value 248 (|post|); now the postamble can be read, so the
\.{DVI} reader discovers all the information needed for typesetting the
pages. Note that it is also possible to skip through the \.{DVI} file at
reasonably high speed to locate a particular page, if that proves
desirable. This saves a lot of time, since \.{DVI} files used in production
jobs tend to be large.
Unfortunately, however, standard \PASCAL\ does not include the ability to
@^system dependencies@>
access a random position in a file, or even to determine the length of a file.
Almost all systems nowadays provide the necessary capabilities, so \.{DVI}
format has been designed to work most efficiently with modern operating systems.
As noted above, \.{DVItoLN03} would need to be modified to limit itself to the
restrictions of standard \PASCAL\ if |random_reading| is defined to be |false|.
@* Font metric data.
The idea behind \.{TFM} files is that typesetting routines like \TeX\
need a compact way to store the relevant information about several
dozen fonts, and computer centers need a compact way to store the
relevant information about several hundred fonts. \.{TFM} files are
compact, and most of the information they contain is highly relevant,
so they provide a solution to the problem.
The information in a \.{TFM} file appears in a sequence of 8-bit bytes.
Since the number of bytes is always a multiple of 4, we could
also regard the file as a sequence of 32-bit words; but \TeX\ uses the
byte interpretation. Note that the bytes are considered to be unsigned
numbers.
@<Glob...@>=
@!tfm_file:byte_file; {a font metric file}
@ The first 24 bytes (6 words) of a \.{TFM} file contain twelve 16-bit
integers that give the lengths of the various subsequent portions
of the file. These twelve integers are, in order:
$$\vbox{\halign{\hfil#&$\null=\null$#\hfil\cr
|@!lf|&length of the entire file, in words;\cr
|@!lh|&length of the header data, in words;\cr
|@!bc|&smallest character code in the font;\cr
|@!ec|&largest character code in the font;\cr
|@!nw|&number of words in the width table;\cr
|@!nh|&number of words in the height table;\cr
|@!nd|&number of words in the depth table;\cr
|@!ni|&number of words in the italic correction table;\cr
|@!nl|&number of words in the lig/kern table;\cr
|@!nk|&number of words in the kern table;\cr
|@!ne|&number of words in the extensible character table;\cr
|@!np|&number of font parameter words.\cr}}$$
They are all nonnegative and less than $2^{15}$. We must have |bc-1<=ec<=255|,
|ne<=256|, and
$$\hbox{|lf=6+lh+(ec-bc+1)+nw+nh+nd+ni+nl+nk+ne+np|.}$$
Note that a font may contain as many as 256 characters (if |bc=0| and |ec=255|),
and as few as 0 characters (if |bc=ec+1|).
Incidentally, when two or more 8-bit bytes are combined to form an integer of
16 or more bits, the most significant bytes appear first in the file.
This is called BigEndian order.
@ The rest of the \.{TFM} file may be regarded as a sequence of ten data
arrays having the informal specification
$$\def\arr$[#1]#2${\&{array} $[#1]$ \&{of} #2}
\vbox{\halign{\hfil\\{#}&$\,:\,$\arr#\hfil\cr
header&|[0..lh-1]stuff|\cr
char\_info&|[bc..ec]char_info_word|\cr
width&|[0..nw-1]fix_word|\cr
height&|[0..nh-1]fix_word|\cr
depth&|[0..nd-1]fix_word|\cr
italic&|[0..ni-1]fix_word|\cr
lig\_kern&|[0..nl-1]lig_kern_command|\cr
kern&|[0..nk-1]fix_word|\cr
exten&|[0..ne-1]extensible_recipe|\cr
param&|[1..np]fix_word|\cr}}$$
The most important data type used here is a |@!fix_word|, which is
a 32-bit representation of a binary fraction. A |fix_word| is a signed
quantity, with the two's complement of the entire word used to represent
negation. Of the 32 bits in a |fix_word|, exactly 12 are to the left of the
binary point; thus, the largest |fix_word| value is $2048-2^{-20}$, and
the smallest is $-2048$. We will see below, however, that all but one of
the |fix_word| values will lie between $-16$ and $+16$.
@ The first data array is a block of header information, which contains
general facts about the font. The header must contain at least two words,
and for \.{TFM} files to be used with Xerox printing software it must
contain at least 18 words, allocated as described below. When different
kinds of devices need to be interfaced, it may be necessary to add further
words to the header block.
\yskip\hang|header[0]| is a 32-bit check sum that \TeX\ will copy into the
\.{DVI} output file whenever it uses the font. Later on when the \.{DVI}
file is printed, possibly on another computer, the actual font that gets
used is supposed to have a check sum that agrees with the one in the
\.{TFM} file used by \TeX. In this way, users will be warned about
potential incompatibilities. (However, if the check sum is zero in either
the font file or the \.{TFM} file, no check is made.) The actual relation
between this check sum and the rest of the \.{TFM} file is not important;
the check sum is simply an identification number with the property that
incompatible fonts almost always have distinct check sums.
@^check sum@>
\yskip\hang|header[1]| is a |fix_word| containing the design size of the
font, in units of \TeX\ points (7227 \TeX\ points = 254 cm). This number
must be at least 1.0; it is fairly arbitrary, but usually the design size
is 10.0 for a ``10 point'' font, i.e., a font that was designed to look
best at a 10-point size, whatever that really means. When a \TeX\ user
asks for a font `\.{at} $\delta$ \.{pt}', the effect is to override the
design size and replace it by $\delta$, and to multiply the $x$ and~$y$
coordinates of the points in the font image by a factor of $\delta$
divided by the design size. {\sl All other dimensions in the\/\ \.{TFM}
file are |fix_word|\kern-1pt\ numbers in design-size units.} Thus, for example,
the value of |param[6]|, one \.{em} or \.{\\quad}, is often the |fix_word|
value $2^{20}=1.0$, since many fonts have a design size equal to one em.
The other dimensions must be less than 16 design-size units in absolute
value; thus, |header[1]| and |param[1]| are the only |fix_word| entries in
the whole \.{TFM} file whose first byte might be something besides 0 or
255. @^design size@>
\yskip\hang|header[2..11]|, if present, contains 40 bytes that identify
the character coding scheme. The first byte, which must be between 0 and
39, is the number of subsequent ASCII bytes actually relevant in this
string, which is intended to specify what character-code-to-symbol
convention is present in the font. Examples are \.{ASCII} for standard
ASCII, \.{TeX text} for fonts like \.{cmr10} and \.{cmti9}, \.{TeX math
extension} for \.{cmex10}, \.{XEROX text} for Xerox fonts, \.{GRAPHIC} for
special-purpose non-alphabetic fonts, \.{UNSPECIFIED} for the default case
when there is no information. Parentheses should not appear in this name.
(Such a string is said to be in {\mc BCPL} format.)
@^coding scheme@>
\yskip\hang|header[12..16]|, if present, contains 20 bytes that name the
font family (e.g., \.{CMR} or \.{HELVETICA}), in {\mc BCPL} format.
This field is also known as the ``font identifier.''
@^family name@>
@^font identifier@>
\yskip\hang|header[17]|, if present, contains a first byte called the
|seven_bit_safe_flag|, then two bytes that are ignored, and a fourth byte
called the |face|. If the value of the fourth byte is less than 18, it has
the following interpretation as a ``weight, slope, and expansion'': Add 0
or 2 or 4 (for medium or bold or light) to 0 or 1 (for roman or italic) to
0 or 6 or 12 (for regular or condensed or extended). For example, 13 is
0+1+12, so it represents medium italic extended. A three-letter code
(e.g., \.{MIE}) can be used for such |face| data.
\yskip\hang|header[18..@twhatever@>]| might also be present; the individual
words are simply called |header[18]|, |header[19]|, etc., at the moment.
@ Next comes the |char_info| array, which contains one |char_info_word|
per character. Each |char_info_word| contains six fields packed into
four bytes as follows.
\yskip\hang first byte: |width_index| (8 bits)\par
\hang second byte: |height_index| (4 bits) times 16, plus |depth_index|
(4~bits)\par
\hang third byte: |italic_index| (6 bits) times 4, plus |tag|
(2~bits)\par
\hang fourth byte: |remainder| (8 bits)\par
\yskip\noindent
The actual width of a character is |width[width_index]|, in design-size
units; this is a device for compressing information, since many characters
have the same width. Since it is quite common for many characters
to have the same height, depth, or italic correction, the \.{TFM} format
imposes a limit of 16 different heights, 16 different depths, and
64 different italic corrections.
Incidentally, the relation |width[0]=height[0]=depth[0]=italic[0]=0|
should always hold, so that an index of zero implies a value of zero.
The |width_index| should never be zero unless the character does
not exist in the font, since a character is valid if and only if it lies
between |bc| and |ec| and has a nonzero |width_index|.
@ The remaining details of the format of \.{TFM} files need not concern us
further; the interested reader is referred to Knuth's standard \.{TFtoPL}
@^Knuth, D.~E.@>
@.TFtoPL@>
program for the whole picture.
@* Input from binary files.
We have seen that a \.{DVI} file is a sequence of 8-bit bytes. The bytes
appear physically in what is called a `|packed file of 0..255|'
in \PASCAL\ lingo.
Packing is system dependent, and many \PASCAL\ systems fail to implement
such files in a sensible way (at least, from the viewpoint of producing
good production software). For example, some systems treat all
byte-oriented files as text, looking for end-of-line marks and such
things. Therefore some system-dependent code is often needed to deal with
binary files, even though most of the program in this section of
\.{DVItoLN03} is written in standard \PASCAL.
@^system dependencies@>
One common way to solve the problem is to consider files of |integer|
numbers, and to convert an integer in the range $-2^{31}\L x<2^{31}$ to
a sequence of four bytes $(a,b,c,d)$ using the following code, which
avoids the controversial integer division of negative numbers:
$$\vbox{\halign{#\hfil\cr
|if x>=0 then a:=x div @'100000000|\cr
|else begin x:=(x+@'10000000000)+@'10000000000; a:=x div @'100000000+128;|\cr
\quad|end|\cr
|x:=x mod @'100000000;|\cr
|b:=x div @'200000; x:=x mod @'200000;|\cr
|c:=x div @'400; d:=x mod @'400;|\cr}}$$
The four bytes are then kept in a buffer and output one by one. (On 36-bit
computers, an additional division by 16 is necessary at the beginning.
Another way to separate an integer into four bytes is to use/abuse
\PASCAL's variant records, storing an integer and retrieving bytes that are
packed in the same place; {\sl caveat implementor!\/}) It is also desirable
in some cases to read a hundred or so integers at a time, maintaining a
larger buffer.
Although plain \.{DVItype} sticks to simple \PASCAL, for reasons of clarity,
(even if such simplicity is sometimes unrealistic), we do things rather
differently under Vax/VMS, reading each file one disk block at a time.
@d VAX_block_length=512
@<Types...@>=
@!eight_bits=[byte] 0..255; {unsigned one-byte quantity}
@!sixteen_bits=[word] 0..65535; {unsigned two-byte quantity}
@!byte_block=packed array [0..VAX_block_length-1] of eight_bits;
@!byte_file=packed file of byte_block;
@ To prepare these files for input, we |reset| them. An extension of
\PASCAL\ is needed in the case of |tfm_file|, since we want to associate
it with external files whose names are specified dynamically (i.e., not
known at compile time). The following code uses the VAX-\PASCAL\ |open|
statement; where |open(f,s)| opens the file associated with file variable |f|
using the name provided by string variable |s|; once the file variable has
been associated with the correctly named file, the |reset| statement positions
the file for reading in the usual manner.
In the case of opening the |tfm_file|, we firstly close any earlier
one which may already have been opened. If |eof(f)| is true immediately after
the file has been opened, we assume that no file named |s| is accessible.
@^system dependencies@>
To improve the visual appearance of various VAX-\PASCAL\ file manipulation
statements, the following definition is used to represent the use of the
`named parameter' passing mechanism which instructs that any errors detected
shall be ignored, rather than causing a run-time error.
@d VAX_continue==@=error@>:=@=continue@>
@p procedure open_dvi_file; {prepares to read packed bytes in |dvi_file|}
begin reset(dvi_file);
dvi_count:=0; cur_block:=0;
cur_loc:=0;
end;
@#
procedure open_tfm_file; {prepares to read packed bytes in |tfm_file|}
var @!trimmed_name:file_spec; {Holds trimmed version of |cur_name|}
@!i,@!j:0..name_length; {Indices into |cur_name|}
begin close(tfm_file,VAX_continue);
trimmed_name:=''; {Initialize to empty string}
i:=name_length;
while cur_name[i]=' ' do decr(i); {we know that the name isn't all spaces!}
for j:=1 to i do trimmed_name:=trimmed_name+cur_name[j];
open(tfm_file,cur_name,@=readonly@>,VAX_continue);
if status(tfm_file)<>0 then
abort('Font not loaded, TFM file ',trimmed_name,' can''t be opened');
@:fatal error Font not loaded TFM file}{\qquad\.{TFM file can't be opened}@>
reset(tfm_file,VAX_continue);
tfm_count:=0;
end;
@ If you looked carefully at the preceding code, you probably asked,
``What are |cur_loc| and |cur_name|?'' Good question. They're global
variables: |cur_loc| is the number of the byte about to be read next from
|dvi_file|, and |cur_name| is a string variable that will be set to the
current font metric file name before |open_tfm_file| is called. When the
\.{DVI} file is first |open|ed, we'll make use of the |@=user_action@>| facility
of the VAX |open| procedure to note that actual file length in |dvi_size|.
This can then be used as the value of the |dvi_length| function.
Since we read a VAX/RMS blockfull of bytes from the \.{DVI} file, variable
|dvi_count| allows us to keep track of which byte of the current block is
the next to be ``read''. Similarly, |tfm_count| tracks the current byte of
the block read from |tfm_file|. |cur_block| tracks which particular block
of the whole file is currently in the buffer associated with |dvi_file|;
this is used when |random_reading| is used to skip around in the file. No
analogous variable is required for the |tfm_file|.
@<Glob...@>=
@!cur_loc:integer; {where we are about to look, in |dvi_file|}
@!cur_name:packed array[1..name_length] of char; {external name,
with no lower case letters}
@!dvi_size:integer; {Actual length of \.{DVI} file, in bytes}
@!dvi_count:integer; {number of bytes read from current block of |dvi_file|}
@!cur_block:integer; {relative position of current block of |dvi_file|}
@!tfm_count:integer; {number of bytes read from current block of |tfm_file|}
@ It turns out to be convenient to read four bytes at a time, when we are
inputting from \.{TFM} files. The input goes into global variables
|b0|, |b1|, |b2|, and |b3|, with |b0| getting the first byte and |b3|
the fourth.
@<Glob...@>=
@!b0,@!b1,@!b2,@!b3: eight_bits; {four bytes input at once}
@ The |read_tfm_word| procedure sets |b0| through |b3| to the next
four bytes in the current \.{TFM} file.
@^system dependencies@>
@d read_tfm_file(#)==begin
if tfm_count=VAX_block_length then begin
get(tfm_file,VAX_continue); tfm_count:=0;
end;
#:=tfm_file^[tfm_count];
incr(tfm_count);
end
@p procedure read_tfm_word;
begin read_tfm_file(b0); read_tfm_file(b1);
read_tfm_file(b2); read_tfm_file(b3);
end;
@ We shall use another set of simple functions to read the next byte or
bytes from |dvi_file|. There are seven possibilities, each of which is
treated as a separate function in order to minimize the overhead for
subroutine calls.
@^system dependencies@>
Things aren't quite as simple as that, because when processing |dvi|
commands to set characters on the page, it may prove necessary to take
further |dvi| commands from a sequence read from a virtual font (\\{q.v.}).
Therefore, these routines have the option of reading |dvi| bytes from
|vf[vf_take]|. When reading |dvi| bytes from the \.{DVI} file, |vftake<0|.
(\\{N.B.} |vf_take| and |vf_ptr| will be dealt with \&{much} later.)
@d read_dvi_file(#)==if vf_take<0 then
begin
while dvi_count>=VAX_block_length do begin
get(dvi_file,VAX_continue);
dvi_count:=dvi_count-VAX_block_length; incr(cur_block);
end;
#:=dvi_file^[dvi_count];
incr(dvi_count); incr(cur_loc)
end
else
begin {take ``input'' from stored sequence}
if vf_take=vf_ptr then abort('no more virtual font bytes');
@:fatal error no more virtual font bytes}{\quad\.{no more virtual font bytes}@>
#:=vf[vf_take]; incr(vf_take)
end
@p function get_byte:integer; {returns the next byte, unsigned}
var b:eight_bits;
begin if eof(dvi_file) then get_byte:=0
else begin read_dvi_file(b); get_byte:=b;
end;
end;
@#
function signed_byte:integer; {returns the next byte, signed}
var b:eight_bits;
begin read_dvi_file(b);
if b<128 then signed_byte:=b @+ else signed_byte:=b-256;
end;
@#
function get_two_bytes:integer; {returns the next two bytes, unsigned}
var a,@!b:eight_bits;
begin read_dvi_file(a); read_dvi_file(b);
get_two_bytes:=a*256+b;
end;
@#
function signed_pair:integer; {returns the next two bytes, signed}
var a,@!b:eight_bits;
begin read_dvi_file(a); read_dvi_file(b);
if a<128 then signed_pair:=a*256+b
else signed_pair:=(a-256)*256+b;
end;
@#
function get_three_bytes:integer; {returns the next three bytes, unsigned}
var a,@!b,@!c:eight_bits;
begin read_dvi_file(a); read_dvi_file(b); read_dvi_file(c);
get_three_bytes:=(a*256+b)*256+c;
end;
@#
function signed_trio:integer; {returns the next three bytes, signed}
var a,@!b,@!c:eight_bits;
begin read_dvi_file(a); read_dvi_file(b); read_dvi_file(c);
if a<128 then signed_trio:=(a*256+b)*256+c
else signed_trio:=((a-256)*256+b)*256+c;
end;
@#
function signed_quad:integer; {returns the next four bytes, signed}
var a,@!b,@!c,@!d:eight_bits;
begin read_dvi_file(a); read_dvi_file(b); read_dvi_file(c); read_dvi_file(d);
if a<128 then signed_quad:=((a*256+b)*256+c)*256+d
else signed_quad:=(((a-256)*256+b)*256+c)*256+d;
end;
@ Finally we come to the routines that are used only if |random_reading| is
|true|. The driver program below needs two such routines: |dvi_length| should
compute the total number of bytes in |dvi_file|, possibly also
causing |eof(dvi_file)| to be true; and |move_to_byte(n)|
should position |dvi_file| so that the next |get_byte| will read byte |n|,
starting with |n=0| for the first byte in the file.
@^system dependencies@>
Such routines are, of course, highly system dependent. They are implemented
in this program by
\yskip\hang(1) utilizing the optional |user_action| parameter of the |open|
procedure, this allows us to determine the length of the file when it is
first opened; and
\yskip\hang(2), using the VAX-\PASCAL\ |find| procedure to read a specific
block of the file, and positioning to the correct byte by resetting the
|byte_block| pointer appropriately.
\noindent We also make extensive use of VAX-\PASCAL's |varying array|
extension, which
facilitates the handling of variable length character strings; without them, it
would be necessary to keep track of the length of each such string, in the
fashion used by \.{DVItype} to store font names.
The VAX-\PASCAL\ |find(f,block)| procedure reads (into file variable
|f|) the specified record of the file; these are numbered from 1 upwards,
and, since we are working with block-oriented files, each record consists of
an entire disk block.
@f varying==array
@d VAX_find_block==@= find@>
@p function dvi_length:integer;
begin dvi_length:=dvi_size; if dvi_size<0 then
abort('Internal error (no dvi_length)');
@:fatal error internal error no dvi}{\qquad\.{(no dvi_length)}@>
end;
@#
procedure move_to_byte(n:integer);
begin if n div VAX_block_length <> cur_block then
begin cur_block:=n div VAX_block_length;
VAX_find_block(dvi_file,cur_block+1,VAX_continue)
end;
dvi_count:=n mod VAX_block_length; cur_loc:=n;
end;
@* Reading the font information.
\.{DVI} file format does not include information about character widths, since
that would tend to make the files a lot longer. But a program that reads
a \.{DVI} file is supposed to know the widths of the characters that appear
in \\{set\_char} commands. Therefore \.{DVItoLN03} looks at the font metric
(\.{TFM}) files for the fonts that are involved.
@.TFM {\rm files}@>
The character-width data appears also in other files (e.g., in \.{GF} files
that specify bit patterns for digitized characters);
thus, it is usually possible for \.{DVI} reading programs to get by with
accessing only one file per font. \.{DVItype} has a comparatively easy
task in this regard, since it needs only a few words of information from
each font; the \.{DVItoLN03} program has to go to some pains to
deal with complications that arise when a large number of large font files
all need to be accessed simultaneously.
@ \.{DVItype} needed to know only two things about a
given character |c| in a given font |f|: (1)~Is |c| a legal character
in~|f|? (2)~If so, what is the width of |c|? It also needed to know the
symbolic name of each font, so it could be printed out, and to know
the approximate size of inter-word spaces in each font.
For \.{DVItoLN03}, some further information is required, such as which
character in a downloaded font is to be used to image character |c| in font
|f|, and whether this imaging can be performed by the LN03 itself (from
stored downloaded fonts) or whether the glyph needs to be imaged by
downloading a bitmapped representation each time it occurs, or even replacing
it by a solid rule if no suitable file of bitmaps is available.
The answers to these questions appear implicitly in the following data
structures. The current number of known fonts is |nf|. Each known font has
an internal number |f|, where |0<=f<nf|; the external number of this font,
i.e., its font identification number in the \.{DVI} file, is |font_num[f]|,
and the external name of this font is the string that occupies positions
|font_name[f]| through |font_name[f+1]-1| of the array |names|. The latter
array consists of |ASCII_code| characters, and |font_name[nf]| is its first
unoccupied position.
Various metrical data is saved for comparison with values to be read later
from the file(s) containing the characters' bitmaps: |font_check_sum|,
|font_scaled_size| and |font_design_size|.
A horizontal motion in the range |-4*font_space[f]<h<font_space[f]|
will be treated as a `kern' that merely advances the program's conception of
physical horizontal position; larger movements will result in computation of a
freshly rounded position, thus eliminating cumulative errors which might
otherwise lead to unsightly character placement on the page.
The legal characters run from |font_bc[f]| to |font_ec[f]|, inclusive; more
precisely, a given character |c| is valid in font |f| if and only if
|font_bc[f]<=c<=font_ec[f]| and |char_width(f)(c)<>invalid_width|.
To cut down on page faulting with small working sets, the dimensions of each
character are held within the |glyph_map| structure (see following section),
so that all information relating to a particular glyph is held in one
contiguous section of memory: the original \.{DVItype}, and earlier versions
of \.{DVItoLN03}, used to have separate arrays |width[0..max_glyphs]|, etc.,
which could require references to locations separated by |max_glyphs|
integers, making inefficient use of virtual memory.
To obviate reloading of fonts with identical names and scaling, a further
array |font_map[f]| ordinarily holds the value |f|, but can point to a
different set of metrics if such multiple loading has been avoided.
Virtual fonts will almost certainly introduce different physical fonts; such
fonts are given a relative reference number internally, but we will want to
supply a value for the |font_num| array that's unique to each such font.
Therefore we keep track of the highest font number allocated by \TeX\ and
then use this as a base value when reading virtual fonts.
@d invalid_width==@'17777777777
@<Glob...@>=
@!nf:0..max_fonts; {the number of known fonts}
@!font_num:array [0..max_fonts] of integer; {external font numbers}
@!font_name:array [0..max_fonts] of 0..name_size; {starting positions
of external font names}
@!names:array [0..name_size] of ASCII_code; {characters of names}
@!font_check_sum:array [0..max_fonts] of integer; {check sums}
@!font_scaled_size:array [0..max_fonts] of integer; {scale factors}
@!font_design_size:array [0..max_fonts] of integer; {design sizes}
@!font_space:array [0..max_fonts] of integer; {boundary between ``small''
and ``large'' spaces}
@!font_bc:array [0..max_fonts] of integer; {beginning characters in fonts}
@!font_ec:array [0..max_fonts] of integer; {ending characters in fonts}
@!font_map:array [0..max_fonts] of 0..max_fonts; {internal cross-reference}
@!highest_font:0..max_fonts;
@ An array |glyphs| has one element for each of the potential characters in
each font. Each element is a record of type |glyph_info| (defined below).
When a font definition is first read, during the gathering of font usage
statistics in the first pass, the row of this array corresponding to the
font being defined is set to indicate that every character is |unused|. As
any character in this font is found in a wanted page during that first pass,
the usage is noted. Later revisions take place when mapping characters from
\TeX's fonts to those dowloaded to the printer.
During the second pass through the wanted pages, the character codes in
|glyphs| are used to generate the \.{LN3} file.
|glyph_map(f)(c)=glyphs[glyph_base[f]+c]| permits us access to information
relating to an individual glyph; fields of this record, named |width|, |height|
and |depth| are accessible through macro
|char_width(f)(c)=glyphs[glyph_base[f]+c].width| and its analogues; this works
because |glyph_base[f]| contains the index into |glyphs| at which character~0
appears (or would appear, if |font_bc[f]>0|). |glyph_ptr| is the first unused
position of the |glyphs| array.
@d char_glyph_end(#)==#]
@d glyph_map(#)==glyphs[glyph_base[#]+char_glyph_end
@d char_width_end(#)==#].width
@d char_height_end(#)==#].height
@d char_depth_end(#)==#].depth
@d char_width(#)==glyphs[glyph_base[#]+char_width_end
@d char_height(#)==glyphs[glyph_base[#]+char_height_end
@d char_depth(#)==glyphs[glyph_base[#]+char_depth_end
@<Glob...@>=
@!glyphs: array [0..max_glyphs] of glyph_info;
@!glyph_base : array [0..max_fonts] of integer; {index into |glyphs|}
@!glyph_ptr : 0..max_glyphs; {index into |glyphs|, |width|, |height| and
|depth|}
@ The array |glyphs| contains elements whose fields are used as follows:
\yskip\centerline{\vbox{\offinterlineskip\hrule
\halign{\vrule#&\strut\quad#\hfil\quad&\vrule#&\quad#\hfil\quad&\vrule#\cr
height2pt&\omit&&\omit&\cr
&\hfil Field Name\quad&&Used for&\cr
height2pt&\omit&&\omit&\cr
\noalign{\hrule}
height2pt&\omit&&\omit&\cr
&|width|&&Character width in \.{DVI} units&\cr
&|height|&&Character height in \.{DVI} units&\cr
&|depth|&&Character depth in \.{DVI} units&\cr
&|font_code|&&Number of downloaded LN03 font containing glyph&\cr
&|char_code|&&LN03 character code to access glyph&\cr
&|loaded|&&Indicates status of glyph, as below&\cr
height2pt&\omit&&\omit&\cr
}\hrule
}
}
The values that may be assumed by the |loaded| field are as follows:
\item{$\bullet$} when a font is first met, all characters are set with
the value |unused|;
\item{$\bullet$} as the document is scanned, whenever a particular glyph
is used this field is set to |wanted|;
\item{$\bullet$} other values are set once the wanted pages have been
scanned, and the character font downloads created.
\noindent Further values for the |loaded| field, and other fields, will be
defined for accessing virtual fonts.
@<Types...@>==
@!download_status = (@!unused,@!wanted@|@<Other values of |download_status|@>@+);@/
@!glyph_info =
packed record
@!width:integer; {character width, in \.{DVI} units}
@!height:integer; {character height, in \.{DVI} units}
@!depth:integer; {character depth, in \.{DVI} units}
@!char_code : eight_bits;
@!loaded : download_status;
@!font_code : 0..max_fonts;
@<Other fields of |glyph_info|@>@;
end;
@ @<Set init...@>=
nf:=0; glyph_ptr:=0; font_name[0]:=0; font_space[0]:=0; font_map[0]:=0;
highest_font:=0;
@ It is, of course, a simple matter to print the name of a given font.
@p procedure print_font(@!f:integer); {|f| is an internal font number}
var k:0..name_size; {index into |names|}
begin if f=nf then print('UNDEFINED!')
@.UNDEFINED@>
else begin for k:=font_name[f] to font_name[f+1]-1 do
print(xchr[names[k]]);
end;
end;
@ Auxiliary arrays |in_width|, |in_height| and |in_depth| are used to hold
the relevant sections of the |tfm_file| as they are
input. The global variable |tfm_check_sum| is set to the check sum that
appears in the current \.{TFM} file.
@<Glob...@>=
@!in_width:array[0..255] of integer; {\.{TFM} width data in \.{DVI} units}
@!in_height:array[0..15] of integer; {\.{TFM} height data in \.{DVI} units}
@!in_depth:array[0..15] of integer; {\.{TFM} depth data in \.{DVI} units}
@!tfm_check_sum:integer; {check sum found in |tfm_file|}
@ Here is a procedure that absorbs the necessary information from a
\.{TFM} file, assuming that the file has just been successfully reset
so that we are ready to read its first byte. (A complete description of
\.{TFM} file format appears in the documentation of \.{TFtoPL} and will
not be repeated here.) The procedure does not check the \.{TFM} file
for validity, nor does it give explicit information about what is
wrong with a \.{TFM} file that proves to be invalid; \.{DVI}-reading
programs need not do this, since \.{TFM} files are almost always valid,
and since the \.{TFtoPL} utility program has been specifically designed
to diagnose \.{TFM} errors. The procedure simply returns |false| if it
detects anything amiss in the \.{TFM} data.
There is a parameter, |z|, which represents the scaling factor being
used to compute the font dimensions; it must be in the range $0<z<2^{27}$.
@p function in_TFM(@!z:integer):boolean; {input \.{TFM} data or return |false|}
label 9997, {go here when the format is bad}
9998, {go here when the information cannot be loaded}
9999; {go here to exit}
var k:integer; {index for loops}
@!lh:integer; {length of the header data, in four-byte words}
@!nw:integer; {number of words in the width table}
@!nh:integer; {number of words in the height table}
@!nd:integer; {number of words in the depth table}
@!gp:0..max_glyphs; {new value of |glyph_ptr| after successful input}
@!alpha,@!beta:integer; {quantities used in the scaling computation}
begin @<Read past the header data; |goto 9997| if there is a problem@>;
@<Store character-width indices at the end of the |width| table@>;
@<Read and convert the width values, setting up the |in_width| table@>;
@<Move the widths from |in_width| to |width|, and append |pixel_width| values@>;
glyph_ptr:=gp; in_TFM:=true; goto 9999;
9997: error('---not loaded, TFM file is bad');
@.TFM file is bad@>
9998: in_TFM:=false;
9999: end;
@ @<Read past the header...@>=
read_tfm_word; lh:=b2*256+b3;
read_tfm_word; font_bc[nf]:=b0*256+b1; font_ec[nf]:=b2*256+b3;
if font_ec[nf]<font_bc[nf] then font_bc[nf]:=font_ec[nf]+1;
@<Mark all glyphs of font |nf| as |unused|@>;
{now |gp=glyph_ptr+font_ec[nf]-font_bc[nf]+1|}
read_tfm_word; nw:=b0*256+b1;
if (nw=0)or(nw>256) then goto 9997;
nh:=b2*256+b3;
if (nh=0)or(nh>16) then goto 9997;
read_tfm_word; nd:=b0*256+b1;
if (nd=0)or(nd>16) then goto 9997;
for k:=2 to 3+lh do
begin if eof(tfm_file) then goto 9997;
read_tfm_word;
if k=4 then
if b0<128 then tfm_check_sum:=((b0*256+b1)*256+b2)*256+b3
else tfm_check_sum:=(((b0-256)*256+b1)*256+b2)*256+b3;
end;
@ Whenever the program reads a \.{TFM} file, either during the main
processing loop, or when such files are read in whilst mapping virtual fonts
to physical ones, the |glyph_map| row corresponding to the physical file is
preset to indicate that none of the glyphs are used, and also to signify
that, in the absence of further information, the characters are taken from
the current font. This little segment of code used to appear in-line within
procedure |in_TFM|.
We also indicate here that no glyphs from the font as a whole are currently
used. Once a glyph is referenced (either directly, or from within a
character packet in a virtual font), we'll mark the font as |wanted|, thus
indicating (so far as we currently know) the font \\{is} physical; this may
be changed later, if the font is found to be virtual. This information is
stored in yet another array, |font_type|.
@<Mark all glyphs of font |nf| as |unused|@>=
font_type[nf]:=unused;
if glyph_ptr+font_ec[nf]-font_bc[nf]+1 > max_glyphs then
capacity_exceeded('too many glyphs: ',glyph_ptr+font_ec[nf]-font_bc[nf]+1:1,
' > ',max_glyphs:1);
@:capacity exceeded too many glyphs}{\quad\.{too many glyphs}@>
glyph_base[nf]:=glyph_ptr-font_bc[nf];
gp:=glyph_ptr+font_ec[nf]-font_bc[nf]+1;
for m:= glyph_ptr to gp-1 do
with glyphs[m] do
begin
loaded := unused;
font_code := nf
end
@ At this point, we've arrived at the start of the |char_info| section of
the |tfm_file|; from each 32-bit word, we extract the indices into the
|width|, |height| and |depth| sections of the file.
@<Store character-width indices...@>=
if gp>0 then for k:=glyph_ptr to gp-1 do
with glyphs[k] do
begin read_tfm_word;
if b0>nw then goto 9997;
width:=b0;
if (b1 div 16)>nh then goto 9997;
height:=b1 div 16;
if (b1 mod 16)>nd then goto 9997;
depth:=b1 mod 16;
end;
@ The most important part of |in_TFM| is the width computation, which
involves multiplying the relative widths in the \.{TFM} file by the
scaling factor in the \.{DVI} file. This fixed-point multiplication
must be done with precisely the same accuracy by all \.{DVI}-reading programs,
in order to validate the assumptions made by \.{DVI}-writing programs
like \TeX82.
Let us therefore summarize what needs to be done. Each width in a \.{TFM}
file appears as a four-byte quantity called a |fix_word|. A |fix_word|
whose respective bytes are $(a,b,c,d)$ represents the number
$$x=\left\{\vcenter{\halign{$#$,\hfil\qquad&if $#$\hfil\cr
b\cdot2^{-4}+c\cdot2^{-12}+d\cdot2^{-20}&a=0;\cr
-16+b\cdot2^{-4}+c\cdot2^{-12}+d\cdot2^{-20}&a=255.\cr}}\right.$$
(No other choices of $a$ are allowed, since the magnitude of a \.{TFM}
dimension must be less than 16.) We want to multiply this quantity by the
integer~|z|, which is known to be less than $2^{27}$. Let $\alpha=16z$.
If $|z|<2^{23}$, the individual multiplications $b\cdot z$, $c\cdot z$,
$d\cdot z$ cannot overflow; otherwise we will divide |z| by 2, 4, 8, or
16, to obtain a multiplier less than $2^{23}$, and we can compensate for
this later. If |z| has thereby been replaced by $|z|^\prime=|z|/2^e$, let
$\beta=2^{4-e}$; we shall compute
$$\lfloor(b+c\cdot2^{-8}+d\cdot2^{-16})\,z^\prime/\beta\rfloor$$ if $a=0$,
or the same quantity minus $\alpha$ if $a=255$. This calculation must be
done exactly, for the reasons stated above; the following program does the
job in a system-independent way, assuming that arithmetic is exact on
numbers less than $2^{31}$ in magnitude.
@<Read and convert the width values...@>=
@<Replace |z| by $|z|^\prime$ and compute $\alpha,\beta$@>;
for k:=0 to nw-1 do
begin read_tfm_word;
in_width[k]:=(((((b3*z)div@'400)+(b2*z))div@'400)+(b1*z))div beta;
if b0>0 then if b0<255 then goto 9997
else in_width[k]:=in_width[k]-alpha;
end;
@<Read and convert the height and depth values@>
@ Similarly, we store away the sets of distinct character heights and
depths; we don't need to convert |z| again.
@<Read and convert the height and depth values@>=
for k:=0 to nh-1 do
begin read_tfm_word;
in_height[k]:=(((((b3*z)div@'400)+(b2*z))div@'400)+(b1*z))div beta;
if b0>0 then if b0<255 then goto 9997
else in_height[k]:=in_height[k]-alpha;
end;
for k:=0 to nd-1 do
begin read_tfm_word;
in_depth[k]:=(((((b3*z)div@'400)+(b2*z))div@'400)+(b1*z))div beta;
if b0>0 then if b0<255 then goto 9997
else in_depth[k]:=in_depth[k]-alpha;
end
@ @<Replace |z|...@>=
begin alpha:=16*z; beta:=16;
while z>=@'40000000 do
begin z:=z div 2; beta:=beta div 2;
end;
end
@ A \.{DVI}-reading program usually works with font files instead of
\.{TFM} files, so \.{DVItype} is atypical in that respect. \.{DVItoLN03}
actually checks information in the \.{TFM} file, \&{and} also reads an
appropriate \.{PXL} file. Font files
should, however, contain exactly the same character width data that is
found in the corresponding \.{TFM}s; check sums are used to help
ensure this. In addition, font files usually also contain the widths of
characters in pixels, since the device-independent character widths of
\.{TFM} files are generally not perfect multiples of pixels.
The |pixel_width| field of the |glyph_map| contains this information; when
|glyphs[k].width| is the device-independent width of some character in
\.{DVI} units, |glyphs[k].pixel_width| is the corresponding width of that
character in an actual font. The macro |char_pixel_width| is set up to be
analogous to |char_width|.
Similarly, |pixel_height| and |pixel_depth| are available; these are used
for replacing the glyph for a character for which the required bitmaps are
unavailable by the appropriately-sized rule.
@d pixel_width_end(#)==#].pixel_width
@d pixel_height_end(#)==#].pixel_height
@d pixel_depth_end(#)==#].pixel_depth
@d char_pixel_width(#)==glyphs[glyph_base[#]+pixel_width_end
@d char_pixel_height(#)==glyphs[glyph_base[#]+pixel_height_end
@d char_pixel_depth(#)==glyphs[glyph_base[#]+pixel_depth_end
@<Other fields of |glyph_info|@>=
@!pixel_width, @!pixel_height, @!pixel_depth : integer;
@ The following variables are used to hold the conversion factors for
translating device-independent units into device pixels.
@<Glob...@>=
@!conv:real; {converts \.{DVI} units to pixels}
@!true_conv:real; {converts unmagnified \.{DVI} units to pixels}
@!numerator,@!denominator:integer; {stated conversion ratio}
@!mag:integer; {magnification factor times 1000}
@ The following code computes pixel widths by simply rounding the \.{TFM}
widths to the nearest integer number of pixels, based on the conversion factor
|conv| that converts \.{DVI} units to pixels. However, such a simple
formula will not be valid for all fonts, and it will often give results that
are off by $\pm1$ when a low-resolution font has been carefully
hand-fitted. For example, a font designer often wants to make the letter `m'
a pixel wider or narrower in order to make the font appear more consistent.
\.{DVI}-to-printer programs should therefore input the correct pixel width
information from font files whenever there is a chance that it may differ.
A warning message may also be desirable in the case that at least one character
is found whose pixel width differs from |conv*width| by more than a full pixel.
@^system dependencies@>
(And similarly for the height and depth values.)
@d pixel_round(#)==round(conv*(#))
@<Move the widths from |in_width| to |width|, and append |pixel_width| values@>=
if in_width[0]<>0 then goto 9997; {the first width should be zero}
if gp>0 then for k:=glyph_ptr to gp-1 do
with glyphs[k] do
begin
if width=0 then
begin width:=invalid_width; pixel_width:=0;
end
else begin width:=in_width[width];
pixel_width:=pixel_round(width);
end;
height:=in_height[height];
pixel_height:=pixel_round(height);
depth:=in_depth[depth];
pixel_depth:=pixel_round(depth)
end
@* Output to the terminal. Whilst DVItoLN03 is processing \.{DVI} files, it
reports its progress by outputting various messages to the terminal: in
particular, when processing each page, it reports the page number, enclosed in
`\.{[]}' brackets. Under Vax/VMS, output to the terminal is not normally
visible until |writeln| is called, since the operating system buffers output
until a line is complete. However, this would mean, for example, that output
would only appear as each line was filled.
We therefore arrange to |open| the |output| file (synonymous with |term_out|)
specifying null carriage control; output may then be made to appear by calling
|write_ln|, but this will not give the usual accompanying newline. To start
printing on a newline, the character sequence |crlf| must be output
explicitly.
The |term_out| file is used for terminal output.
@^system dependencies@>
The number of characters already output to a terminal line is counted
in |term_offset| so that a suitable line break may be chosen when the line
becomes ``full'.
Messages giving page number strings being processed are contained in a
variable of type |file_spec|, because some messages may contain VAX/VMS file
names.
@d term_out==output {the terminal, considered as an output file}
@<Glob...@>=
@!term_offset : 0..line_length;
@ When each page is processed, the page number (as generated by \TeX) is written
out to the user's terminal, enclosed in `\.{[]}' brackets. The procedure
|monitor| ensures that such output does not overlap the right-margin. It
does this by counting the characters already output to the line; up to
|line_length| characters will be output. We use here the VAX-\PASCAL-specific
function |length| which returns the number of characters in a |varying of char|
which are actually occupied.
@d VAX_length==@= length@>
@p procedure monitor(s : file_spec);
begin if VAX_length(s)+2>line_length-term_offset then {Line is fullish!}
begin write_ln(term_out,crlf); term_offset:=0; end;
if (term_offset>0) and (s[1]='[') then {Space before a |'['|}
begin write(term_out,' '); incr(term_offset); end;
write_ln(term_out,s);
term_offset:=term_offset+VAX_length(s)
end;
@*Displaying error messages. Later we shall make extensive use of the
following definitions, particularly when scanning the \.{DVI} file. However,
they are defined up here so that warnings may be issued by the code that
follows. Normally (when invoked from within |do_page|) the variable |a| will
contain the byte number within the \.{DVI} file at which a problem was
encountered; however, to give |show| something to work with when these macros
are used elsewhere, we also define a constant of that name, which will thus
always be within scope.
@d show(#)==begin
if a<0 then print_ln(#) else print_ln('[',a:1,'] ',#);
if term_offset>0 then write_ln(crlf);
write_ln(term_out,#,crlf); term_offset:=0
end
@d error(#)==begin
history := error_given;
show('Error: ',#)
@.Error:@>
end
@d warning(#)==begin
if history = spotless then history := warning_given;
show('Warning: ',#)
@.Warning:@>
end
@<Constants...@>==
a = -1;
@* Optional modes of output.
\.{DVItoLN03} will print different quantities of information based on some
options that the user must specify: the typeout can be confined to a
restricted subset of the pages by specifying the desired starting page and
the maximum number of pages. Further options permit
the selection of a different position for the origin on the page, which
by default is set to the ``standard'' of one inch from the top and left
edges of the paper, and selection of ``portrait'' or ``landscape'' orientation
for the output on the printed page. All options are selected through the
use of VAX/VMS command qualifiers, but procedure |dialog| could be adapted
to carry out an interactive dialogue with a user on systems without VMS'
excellent facilities for defining commands.
The starting page is specified by giving a sequence of 1 to 10 numbers or
asterisks separated by dots. For example, the specification `\.{1.*.-5}'
can be used to refer to a page output by \TeX\ when $\.{\\count0}=1$
and $\.{\\count2}=-5$. (Recall that |bop| commands in a \.{DVI} file
are followed by ten `count' values.) An asterisk matches any number,
so the `\.*' in `\.{1.*.-5}' means that \.{\\count1} is ignored when
specifying the first page. If several pages match the given specification,
\.{DVItoLN03} will begin with the earliest such page in the file. The
default specification `\.*' (which matches all pages) therefore denotes
the page at the beginning of the file.
This version of \.{DVItoLN03} uses the VMS Command Language Interpreter utility
routines to determine whether command line qualifiers are present, and to
fetch their values, which are then stored in the following variables.
@^Command Language Interpreter@>
@^system dependencies@>
The default options that hold unless over-ridden by command line qualifiers
are:
\yskip\hang$\bullet$ \.{/starting\_page}=\.{*}
\yskip\hang$\bullet$ \.{/number\_of\_pages}=\.{1000000}
\yskip\hang$\bullet$ \.{/top\_margin} = \.{300px}
\yskip\hang$\bullet$ \.{/left\_margin}=\.{300px}
\yskip\hang$\bullet$ \.{/orientation}=\.{portrait}
@<Glob...@>=
@!left_marg,@!top_marg : integer;
@!orientation,@!new_orient,@!cur_orient : page_orientation;
@!h_fuzz,@!v_fuzz:integer; {controls how pedantic program is}
@!page_len,@!page_wid,@!y_min,@!x_min : integer;
@!max_pages:integer; {at most this many |bop..eop| pages will be printed}
@!resolution:real; {pixels per inch}
@!new_mag:integer; {if positive, overrides the postamble's magnification}
@ This type allows us to keep track of whether characters are currently
being imaged in landscape or portrait orientation.
@<Types...@>=
@!page_orientation = (@!portrait,@!landscape);
@ The starting page specification is recorded in two global arrays called
|start_count| and |start_there|. For example, `\.{1.*.-5}' is represented
by |start_there[0]=true|, |start_count[0]=1|, |start_there[1]=false|,
|start_there[2]=true|, |start_count[2]=-5|.
We also set |start_vals=2|, to indicate that count 2 was the last one
mentioned. The other values of |start_count| and |start_there| are not
important, in this example.
@<Glob...@>=
@!start_count:array[0..9] of integer; {count values to select starting page}
@!start_there:array[0..9] of boolean; {is the |start_count| value relevant?}
@!start_vals:0..9; {the last count considered significant}
@!count:array[0..9] of integer; {the count values on the current page}
@ @<Set init...@>=
max_pages:=1000000; start_vals:=0; start_there[0]:=false;
top_marg:=300; left_marg:=300; orientation:=portrait;
@ Here is a simple subroutine that tests if the current page might be the
starting page.
@p function start_match:boolean; {does |count| match the starting spec?}
var k:0..9; {loop index}
@!match:boolean; {does everything match so far?}
begin match:=true;
for k:=0 to start_vals do
if start_there[k]and(start_count[k]<>count[k]) then match:=false;
start_match:=match;
end;
@ The global variable |buf_ptr| is used while scanning each line of input;
it points to the first unread character in |buffer|, into which the command
line interpreter places strings by interaction with the CLI interface
procedures.
@<Glob...@>=
@!buffer: varying[terminal_line_length] of char;
@!buf_ptr:0..terminal_line_length; {the number of characters read}
@ The compiler needs to know about the ``foreign'' routines used for access to
the Command Line Interpreter. VAX-\PASCAL\ permits the ``declaration'' of
external procedures, where the declaration consists of the procedure heading
alone, followed by the reserved word |extern|, in a manner analogous to
standard \PASCAL's |forward| directive.
@^system dependencies@>
We also require the following special definitions, types, variables and
procedures to be able to get information from the command line interpreter.
The macro |VAX_string_or_empty| is used so as not to confuse W{\mc EAVE}'s
formatting of these foreign procedure interfaces.
@d VAX_volatile==@=[volatile]@>
@d VAX_immed==@=%immed @>
@d VAX_external==@=[external]@>
@d VAX_descr==@=%descr @>
@d VAX_ref==@=%ref @>
@d VAX_cli_present==@= cli$present@>
@d VAX_cli_get_value==@= cli$get_value@>
@d VAX_cli_negated==@"000381F8
@d VAX_lib_find_file==@= lib$find_file@>
@#
@f VAX_volatile==do_nothing
@f VAX_descr==var
@f VAX_ref==var
@f VAX_immed==integer
@f VAX_external==var
@#
@d VAX_string_or_empty(#)==VAX_volatile varying[@!#] of char := VAX_immed 0
@f extern==forward
@<Procedures for init...@>=
VAX_external @+function VAX_cli_present(
VAX_descr @!entity:VAX_string_or_empty(s1_len)) : integer; extern;@#@t\2@>
VAX_external @+function VAX_cli_get_value(
VAX_descr @!entity:VAX_string_or_empty(s1_len);
VAX_descr @!returns:VAX_string_or_empty(s2_len);
VAX_ref @!retlen:VAX_volatile sixteen_bits := VAX_immed 0):integer;
extern;@#@t\2@>
VAX_external @+function VAX_lib_find_file(
VAX_descr @!part_spec:VAX_volatile file_spec;
VAX_descr @!result_spec:VAX_volatile file_spec;
VAX_ref @!context:VAX_volatile integer;
VAX_descr @!def_spec:VAX_volatile file_spec := VAX_immed 0;
VAX_descr @!related_spec:VAX_volatile file_spec := VAX_immed 0;
VAX_ref @!stv_addr:VAX_volatile integer := VAX_immed 0;
VAX_ref @!user_flags:VAX_volatile integer := VAX_immed 0): integer;
extern;@#@t\2@>
@ The following function |get_value| inputs the string corresponding to a
command-line qualifier and converts it to its numerical value. Calls of the
|VAX_cli_get_value(s,a)| return to the buffer parameter |a| the value placed
by the user on the command qualifier whose name is given by the string |s|;
being a system service type function, it also reports its success (or failure)
as its result. The other system service used is |VAX_cli_present(s)| which
returns an odd (success) status indication if the qualifier whose name is
given by the string |s| \&{is} present in the command line.
One VAX-\PASCAL\ extension permits string variables to be used in place of
files for reading and writing values. The |VAX_string_read(b,i,...)|
procedure reads values into variables |i,...| from the characters stored in
the string variable |b|. The |VAX_string_status| function returns a value in
a manner entirely analogous to VAX-\PASCAL's external I/O |status| function,
but relating to the success of the previous string read.
@d VAX_string_read==@= readv@>
@d VAX_string_status==@= statusv@>
@p function get_value(qualifier: varying [$u1] of char):integer;
var @!i,@!stat,@!len : integer; {local workspace}
buffer: varying [terminal_line_length] of char; {buffer for qualifier
string}
begin
if odd(VAX_cli_present(qualifier)) then
begin stat:=VAX_cli_get_value(qualifier,buffer); buffer:=buffer+' ';
{append a space to ensure end can be found}
VAX_string_read(buffer,i,VAX_continue);
if VAX_string_status > 0 then
abort('Bad /',qualifier,' value! (=',buffer,')')
@:fatal error Bad qualifier}{\quad \.{Bad /}\meta{qualifier} \.{value}@>
else
get_value:=i
end else get_value:=-1
end;
@ Here is a routine that scans a (possibly signed) integer and computes
the decimal value. If no decimal integer starts at |buf_ptr|, the
value 0 is returned. The integer should be less than $2^{31}$ in
absolute value.
@p function get_integer:integer;
var x:integer; {accumulates the value}
@!negative:boolean; {should the value be negated?}
begin if buffer[buf_ptr]='-' then
begin negative:=true; incr(buf_ptr);
end
else negative:=false;
x:=0;
while (buffer[buf_ptr]>='0')and(buffer[buf_ptr]<='9') do
begin x:=10*x+xord[buffer[buf_ptr]]-"0"; incr(buf_ptr);
end;
if negative then get_integer:=-x @+ else get_integer:=x;
end;
@ One feature of the new DEClaser~2200 (otherwise known as the LN06) is a
capability of selecting from which paper tray a sheet shall be fed; the
printer has two separate trays, which can be loaded with different sizes or
colours of paper: a popular use is to have letterhead paper loaded in one
tray, and plain paper for continuation sheets in the other. Both on this
printer, and on the DEClaser~2100 (LN05), any of five different types of
paper tray can be inserted into (respectively, either, or the only) tray
slot. Paper cassettes are available to hold three different American sizes
of paper (letter, legal or executive), the ISO standard A4 size used in
Europe, or a stack of up to 15 envelopes.
The DEClaser~2200 can be fitted with a separate envelope feeder (in addition
to the two paper trays), which can hold up to 40~envelopes. This envelope
feeder is supported by the program, but we don't (yet!) support one
further option of the LN06 which is a large capacity input tray that can
hold 1000 sheets.
In addition, both the 2100 and 2200 models can await manual feeding of
sheets. Selection of paper source is not supported on the LN03, of course.
\noindent Here are the values that may be listed with the \.{/feed\_tray}
qualifier:
\yskip\hang\.{ALL=}\meta{tray type} All sheets will be fed from the
specified tray.
\yskip\hang\.{FIRST=}\meta{tray type} Feed the first sheet from the
specified tray; subsequent sheets will be fed from the \.{DEFAULT\_TRAY},
unless the next qualifier is used:
\yskip\hang\.{REST=}\meta{tray type} Feed the second and subsequent sheets
from the specified tray; the first sheet will be fed from the
\.{DEFAULT\_TRAY}, unless the \.{FIRST} tray has also been specified.
\noindent\meta{Tray types} can take the following values:
\yskip\hang\.{DEFAULT\_TRAY} Use whichever tray is set as the default by the
printer's initialization sequence.
\yskip\hang\.{TOP\_TRAY} (This is the only tray on the DEClaser~2100.)
\yskip\hang\.{BOTTOM\_TRAY}
\yskip\hang\.{ENVELOPE\_TRAY} Feed envelopes, from the large-capacity
envelope tray option on the DEClaser~2200.
\yskip\hang\.{MANUAL\_FEED} Wait for the operator to insert each sheet
manually.
\noindent The following values look as though they could be defined as a
\PASCAL\ enumeration type, but we need to ensure a particular
mapping to ordinal values, so we engage in a Knuthian trick here.
@^Knuth, D.~E.@>
@:TeX the Program}{{\sl\TeX\ the Program}@>
The values correspond to the $P_s$ parameter of the |DECASTC| (Automatic
Sheet-feeder Tray Control) control sequence.
@d default_tray=0
@d top_tray=1
@d bottom_tray=2
@d envelope_feeder=3
@d manual_feed=99
@<Types...@>=
@!tray_type=default_tray..manual_feed;
@ Here is a procedure which converts the \.{/feed\_tray} qualifier to the
appropriate numeric value.
@p procedure @!read_tray(option:file_spec; var result:tray_type);
begin
result:=default_tray;
if odd(VAX_cli_present(option+'.TOP_TRAY')) then
result:=top_tray
else if odd(VAX_cli_present(option+'.BOTTOM_TRAY')) then
result:=bottom_tray
else if odd(VAX_cli_present(option+'.ENVELOPE_TRAY')) then
result:=envelope_feeder
else if odd(VAX_cli_present(option+'.MANUAL_FEED')) then
result:=manual_feed;
if device_type=ln03 then result:=default_tray
end;
@ The selected options are put into global variables by the |dialog|
procedure, which is called just as \.{DVItoLN03} begins. As already
explained, the |term_out| file is opened during the initialization phase, with
\.{carriage\_control}=\.{none}. Having announced the program's name, the
command line interpreter library procedures are invoked to retrieve the
qualifiers and file name from the command line.
@p procedure dialog;
var k:integer; {loop variable}
begin rewrite(term_out); {prepare the terminal for output}
write_ln(term_out,banner,crlf);@/
@<Determine the desired |start_count| values@>;
@<Determine the desired |max_pages|@>;
@<Determine the desired |resolution|@>;
@<Determine the desired |new_mag|@>;
@<Determine the desired |left_marg| and |top_marg|@>;
@<Determine the desired |orientation|@>;
@<Determine the desired ``fuzzes''@>;
@<Determine the desired |print_mode|@>;
@<Determine the desired \\{feed\_trays}@>;
@<Determine the desired |duplex_by_page_numbers|@>;
@<Print all the selected options@>;
end;
@ @<Determine the desired |start...@>=
start_vals:=0; start_there[0]:=false;
if odd(VAX_cli_present('STARTING_PAGE')) then
begin k:=VAX_cli_get_value('STARTING_PAGE',buffer); buffer:=buffer+' ';
if (buffer[1]<>' ') then begin
buf_ptr:= 1; k:=0;
if buffer[1]<>' ' then
repeat if buffer[buf_ptr]='*' then
begin start_there[k]:=false; incr(buf_ptr);
end
else begin start_there[k]:=true; start_count[k]:=get_integer;
end;
if (k<9)and(buffer[buf_ptr]='.') then
begin incr(k); incr(buf_ptr);
end
else start_vals:=k { anything ill-formed we just stop }
until start_vals=k
end
end
@ @<Determine the desired |max_pages|@>=
max_pages:=1000000;
max_pages:=get_value('NUMBER_OF_PAGES');
if max_pages<=0 then max_pages:=1000000;
@ @<Determine the desired |resolution|@>=
resolution:=300.0; { this is the LN03's resolution }
@ @<Determine the desired |new_mag|@>=
new_mag:=0; { no provision for magnified LN03 output yet }
@ The value associated with the \.{/left\UL margin} and \.{/top\UL margin}
qualifiers consists of a number and a specifier for a \TeX\ \meta{physical
unit}. The values provided are translated into pixels; this uses the same
mechanism as is used for interpreting similar dimensions in \.{\BS special}
commands, so the qualifier is copied into the |spec_par| buffer used for
that interpretation. The characters have to be converted from external
representation into |ASCII_code| for this.
@<Determine the desired |left_marg|...@>=
if odd(VAX_cli_present('LEFT_MARGIN')) then
begin
k:=VAX_cli_get_value('LEFT_MARGIN',buffer); buffer:=buffer+' ';
@<Copy qualifier into |spec_par|@>;
if rd_dimension(left_marg) then
begin
print_ln(' --- assuming pixel units');
write_ln(' --- assuming pixel units',crlf);
term_offset:=0
end
@.assuming pixel units@>
end else left_marg := -1;
if left_marg<0 then left_marg:=300;
if odd(VAX_cli_present('TOP_MARGIN')) then
begin
k:=VAX_cli_get_value('TOP_MARGIN',buffer); buffer:=buffer+' ';
@<Copy qualifier into |spec_par|@>;
if rd_dimension(top_marg) then
begin
print_ln(' --- assuming pixel units');
write_ln(' --- assuming pixel units',crlf);
term_offset:=0
end
@.assuming pixel units@>
end else top_marg := -1;
if top_marg<0 then top_marg:=300;
@ Here's how we copy the |char| values from the |buffer| (whence they have
been read from the command-line qualifier) into the |spec_par| buffer,
converting to |ASCII_code| in the process.
@<Copy qualifier into |spec_par|@>=
buf_ptr:=1; spec_len:=1;
while buf_ptr<=buffer.length do
begin
spec_par[spec_len]:=xord[buffer[buf_ptr]];
incr(spec_len); incr(buf_ptr)
end;
spec_ptr:=1
@ When the paper orientation is selected, we may define the |page_len| and
|page_wid| in terms of the physical limits of the paper; these sizes have
been defined at the start of this \.{web}.
@<Determine the desired |orientation|@>=
if odd(VAX_cli_present('ORIENTATION')) then
begin
if odd(VAX_cli_present('PORTRAIT')) then orientation:=portrait else
if odd(VAX_cli_present('LANDSCAPE')) then orientation:=landscape else
warning('/ORIENTATION must be "PORTRAIT" or "LANDSCAPE"; assuming "PORTRAIT"')
@:Warning: orientation must be}{\quad \.{/ORIENTATION must be ...}@>
end;
if orientation=landscape then
begin page_len:=paper_wid+page_x_min; page_wid:=paper_ht+y_land_min;
y_min:=page_x_min; x_min:=y_land_min end else
begin page_len:=paper_ht+y_port_min; page_wid:=paper_wid+page_x_min;
y_min:=y_port_min; x_min:=page_x_min end;
@ The value associated with the \.{/hfuzz} and \.{/vfuzz}
qualifiers consists of a number and a specifier for a \TeX\ \meta{physical
unit}. The values provided are translated into scaled points, entirely
analogously with the translation of the value on the \.{/top\_margin}
qualifier into pixels.
These ``fuzz'' values are used to control how pedantic the program is when
it discovers characters which extend beyond the right-hand margin as
reported (in the \.{DVI} file) by \TeX. The default values for these
qualifiers is \.{100sp}, which implies that ``errors'' smaller than this
(it's a very small quantity, approximately equal to the wavelength of
visible light) will be ignored. Normally \TeX\ wouldn't dream of exceeding
the margin (well, not without issuing an \.{overfull \\hbox} warning), but
with some macro packages it isn't so fussy. For example, the output of W{\sc
EAVE}, when formatting the \PASCAL\ part of a program, can extend beyond the
margin by up to \.{10pt}. By selecting a larger value of ``fuzz'',
spurious warning messages from DVItoLN03 can be suppressed.
@<Determine the desired ``fuzzes''@>=
if odd(VAX_cli_present('HFUZZ')) then
begin
k:=VAX_cli_get_value('HFUZZ',buffer); buffer:=buffer+' ';
@<Copy qualifier into |spec_par|@>;
if rd_scaled_pt(h_fuzz) then
begin
print_ln(' --- assuming scaled points');
@.assuming scaled points@>
write_ln(' --- assuming scaled points',crlf);
term_offset:=0
end
end else h_fuzz := 0;
decr(h_fuzz); {when used, we watch for |h-max_h| exceeding this value}
if h_fuzz<0 then h_fuzz:=99; {corresponds to default of \.{100sp}}
if odd(VAX_cli_present('VFUZZ')) then
begin
k:=VAX_cli_get_value('VFUZZ',buffer); buffer:=buffer+' ';
@<Copy qualifier into |spec_par|@>;
if rd_scaled_pt(v_fuzz) then
begin
print_ln(' --- assuming scaled points');
@.assuming scaled points@>
write_ln(' --- assuming scaled points',crlf);
term_offset:=0
end
end else v_fuzz := 0;
decr(v_fuzz);
if v_fuzz<0 then v_fuzz:=99
@ The program can support the traditional LN03 (and LN03-plus) printers (but
not the LN03R ScriptPrinter, for which a DVI to PostScript program
@^PostScript@>
is required), the LN05 (DEClaser~2100) and the LN06 (DEClaser~2200).
This variable reminds of which mode is in use:
@<Glob...@>=
@!device_type:(@!ln03,@!ln05,@!ln06);
@ This section determines which value was used with the \.{/DEVICE\_TYPE}
qualifier. It is an error (something's gone wrong with the command
definition file) if this qualifier doesn't have a value.
@<Determine the device type@>=
device_type:=ln03;
if not odd(VAX_cli_present('DEVICE_TYPE')) then
abort('No device type specified')
@:fatal error No device type specified}{\quad\.{No device type specified}@>
else
if odd(VAX_cli_present('DEVICE_TYPE.LN03')) then
device_type:=ln03
else
if odd(VAX_cli_present('DEVICE_TYPE.LN05')) then
device_type:=ln05
else
if odd(VAX_cli_present('DEVICE_TYPE.LN06')) then
device_type:=ln06
@ The new DEClaser printers (models 2100 and 2200, otherwise known as the
LN05 and LN06) are capable of a number of extra ``tricks'', compared with
the original LN03; in particular, the DEClaser~2200 can print on both sides
of the paper (``duplex'' printing). The \.{/print\_mode} qualifier of the
command line interface allows us to select a number of different modes of
operation.
\noindent The possible modes are as follows:
\yskip\hang\.{SIMPLEX} Print on one side of paper only; this is the default,
and the only method compatible with the DEClaser~2100 (LN05) and the
original LN03 (but see below).
\yskip\hang\.{DUPLEX} Print on both sides of the paper; which page gets
printed on the reverse of such a sheet is further controlled by the
\.{/duplex\_by\_page\_numbers} qualifier.
\yskip\hang\.{MASTER} Can be used in conjunction with the \.{DUPLEX} value
to produce a master set of sheets for photocopying or other production
process. The program behaves as if the printer were operating in duplex
mode, but actually prints on only one side of the paper, and ``prints'' a
blank page if it would have left the \\{verso} page blank. This option can
be used with the DEClaser~2100 (LN05), and with the traditional LN03, even
though those printers don't support genuine duplexing.
\yskip\hang\.{TUMBLED} Can be used in conjunction with genuine duplex
printing only (and thus is restricted to the DEClaser~2200). Causes
\\{verso} (left-hand) pages to be printed upside-down, therefore making the
final document suitable for binding along the short edge (long edge in
landscape mode) to make a flip-chart style document.
\yskip\hang\.{NORMAL} The opposite of \.{TUMBLED}. This is the default.
\noindent Once again, we define an `enumeration' type with known values:
@d default_print_mode=0
@d true_simplex_normal=1
@d true_simplex_tumbled=2
@d true_duplex_normal=3
@d true_duplex_tumbled=4
@d duplex_master_normal=5
@d duplex_master_tumbled=6
@d ln03_master=7 {we have to handle this by software, since no printer support}
@<Glob...@>=
@!print_mode:default_print_mode..ln03_master;
@ Here's where we analyse the \.{/print\_mode} qualifier, and set
|print_mode| accordingly:
@<Determine the desired |print_mode|@>=
@<Determine the device type@>;
print_mode:=default_print_mode;
if odd(VAX_cli_present('PRINT_MODE')) then
if odd(VAX_cli_present('PRINT_MODE.DUPLEX')) then
if odd(VAX_cli_present('PRINT_MODE.MASTER')) then
if odd(VAX_cli_present('PRINT_MODE.TUMBLED')) then
print_mode:=duplex_master_tumbled
else print_mode:=duplex_master_normal
else
if odd(VAX_cli_present('PRINT_MODE.TUMBLED')) then
print_mode:=true_duplex_tumbled
else print_mode:=true_duplex_normal
else
if odd(VAX_cli_present('PRINT_MODE.TUMBLED')) then
print_mode:=true_simplex_tumbled
else print_mode:=true_simplex_normal;
if device_type=ln03 then
if print_mode>=duplex_master_normal then
print_mode:=ln03_master
else
print_mode:=default_print_mode
@ Now we decide which tray shall be used for the first and subsequent sheets
@<Determine the desired \\{feed\_trays}@>=
if odd(VAX_cli_present('FEED_TRAY')) then
if odd(VAX_cli_present('FEED_TRAY.ALL')) then
begin
read_tray('FEED_TRAY.ALL',first_tray);
following_tray:=first_tray
end
else
begin
read_tray('FEED_TRAY.FIRST',first_tray);
read_tray('FEED_TRAY.REST',following_tray)
end
@ Here are the variables needed to save the print tray(s) selection; also
one which determines in which manner duplexing shall take place
@<Glob...@>=
@!first_tray,@!following_tray:tray_type;
@!duplex_by_page_numbers:boolean;
@ When we are printing on both sides of the paper, or simulating that
operation with the \.{MASTER} option on the \.{/PRINT\_MODE} qualifier, we
will usually elect to print \\{verso} pages when the page number (held in
|count[0]|) is even, and \\{recto} pages when it is odd (or zero). If two
odd or two even pages appear in succession, the other side of the sheet will
be left blank (or a blank page ejected in \.{MASTER} mode).
This is the default mode of operation, when the \.{/DUPLEX\_BY\_PAGE\_NUMBERS}
qualifier is present; if this qualifier is negated, then the \\{verso} side
of the sheet will be covered with whatever page follows that printed on the
\\{recto} side, regardless of the page number; no blank pages appear, unless
explicitly generated by \TeX.
@<Determine the desired |duplex_by_page_numbers|@>=
duplex_by_page_numbers:=odd(VAX_cli_present('DUPLEX_BY_PAGE_NUMBERS'))
@ After the dialogue is over, we print the options so that the user
can see what \.{DVItoLN03} thought was specified.
These macros allow us to show which |print_mode| and feed tray have been
selected:
@d show_print_mode==begin
print('Print mode is ');
case print_mode of
true_simplex_normal,true_duplex_normal,duplex_master_normal:
print('normal ');
true_simplex_tumbled,true_duplex_tumbled,duplex_master_tumbled:
print('tumbled ')
endcases;
if print_mode=ln03_master then print('ln03 ');
if print_mode>=duplex_master_normal then print('master ');
if print_mode>=true_duplex_normal then
print_ln('duplex')
else
print_ln('simplex')
end
@#
@d show_tray(#)==case # of
default_tray: print_ln('fed from the default tray');
top_tray: print_ln('fed from the top tray');
bottom_tray: print_ln('fed from the bottom tray');
envelope_feeder: print_ln('taken from the envelope feeder');
manual_feed: print_ln('manually fed')
endcases
@<Print all the selected options@>=
print_ln('Options selected:');
@.Options selected@>
print(' Starting page = ');
for k:=0 to start_vals do
begin if start_there[k] then print(start_count[k]:1)
else print('*');
if k<start_vals then print('.')
else print_ln(' ');
end;
print_ln(' Maximum number of pages = ',max_pages:1);
print_ln(' Printing starts ',top_marg:1,' pixels from the top of the paper, and');
print_ln(' ',left_marg:1,' pixels from the left-hand edge');
print_ln(' Orientation = ',orientation);
print_ln(' Resolution = ',resolution:12:8,' pixels per inch');
if new_mag>0 then print_ln(' New magnification factor = ',new_mag/1000:8:3);
if h_fuzz>99 then print_ln(' Horizontal overruns less than ',
(h_fuzz+1)/65536.0:1:2,'pt will be ignored');
if v_fuzz>99 then print_ln(' Depth overruns less than ',
(v_fuzz+1)/65536.0:1:2,'pt will be ignored');
if (first_tray<>default_tray) or (following_tray<>default_tray) then
if first_tray<>following_tray then
begin
print('First sheet is '); show_tray(first_tray);
print('Following sheets are '); show_tray(following_tray)
end
else
begin
print('Paper is '); show_tray(first_tray)
end;
if print_mode<>default_print_mode then
show_print_mode;
if print_mode>=true_duplex_normal then
begin
print('Duplexing is ');
if not duplex_by_page_numbers then print('not ');
print_ln('controlled by page numbers')
end
@* Defining fonts.
\.{DVItoLN03} reads the postamble first and loads
all of the fonts defined there; then it processes the pages. Therefore
a \\{fnt\_def} command should match a previous definition if and only
if the \\{fnt\_def} being processed is not in the postamble.
Since the ``wanted'' pages are read twice, {\it viz.} whilst gathering the
font usage statistics and to perform the actual type setting, font
definitions in the \.{DVI} file will actually be met {\bf three} times. The
original logic of \.{DVItype} using the global variable |in_postamble|
covers the first two occasions during the initial scan and the reading of
the postamble. The variable |substitution_pass| prevents us from getting a
``font already defined'' message when each definition is met for the third
time.
@<Glob...@>=
@!substitution_pass:boolean; {are we performing the actual type setting?}
@!in_postamble:boolean; {are we reading the postamble?}
@ @<Set init...@>=
substitution_pass:=false;
in_postamble:=false;
@ Under the VAX/VMS operating system, no distinction is made between fonts
names \.{cmr10} and \.{CMR10}, for example. However, if \TeX\ encounters
a font name not written in the same case as those of the preloaded
format in use, it will think it is a distinct font (and under some other
operating systems, of course, the case of the name could be significant).
The following function allows us to fold upper-case letters in a font name
to lower-case, and thus avoid such infelicitous duplication.
@p function lower(@!ch:ASCII_code): ASCII_code;
begin
if (ch>="A") and (ch<="Z") then
lower:=ch+"a"-"A"
else
lower:=ch
end;
@ The following subroutine does the necessary things when a \\{fnt\_def}
command is being processed.
@p procedure define_font(@!e:integer); {|e| is an external font number}
var @!f:0..max_fonts; {local index into font data structures}
@!p:integer; {length of the area/directory spec}
@!newly_defined: boolean; {first time font has been defined}
@!n:integer; {length of the font name proper}
@!c,@!q,@!d:integer; {check sum, scaled size, and design size}
@!r:0..name_length; {index into |cur_name|}
@!j,@!k:0..name_size; {indices into |names|}
@!mismatch:boolean; {do names disagree?}
begin if nf=max_fonts then capacity_exceeded('too many fonts (max fonts=',
max_fonts:1,')');
@:capacity exceeded too many fonts}{\quad \.{too many fonts}@>
if e > highest_font then highest_font := e;
font_num[nf]:=e; f:=0; font_map[nf]:=nf;
while font_num[f]<>e do incr(f);
@<Read the font parameters into position for font |nf|, and
print the font name@>;
if in_postamble then
begin if (f<nf) and not substitution_pass then
warning('---this font was already defined!');
@:Warning: this font was already defined}{\quad \.{this font was already defined}@>
end
else begin if f=nf then warning('---this font wasn''t loaded before!');
@:Warning: this font wasn't loaded before}{\quad \.{this font wasn't loaded before}@>
end;
newly_defined:=f=nf;@/
@<Avoid reloading the font metrics, if identical font already present@>;
if f=nf then @<Load the new font, unless there are problems@>
else
begin
@<Check that the current font definition matches the old one@>;
if newly_defined and (font_map[nf]<nf) then
begin
print('---is identical to font ',font_num[f]:1);
@.is identical to font ...@>
incr(nf) {the ``new'' font is officially present, but remapped}
end
end;
end;
@ When we reach the point at which it might be found necessary to read the
\TeX\ font metrics from the \.{TFM} file, we can save a little effort if
we've already loaded a font with identical name and at the same scaled size.
In fact, this is essential with such virtual fonts as \.{recurse}, because
otherwise we might go on loading the same \.{TFM} file \\{ad infinitum}!
Variable |mismatch| is misused in the |while| loop such that the loop will
exit if the name of font |f| is identical to that of font |nf|. If the
names differ, the loop continues until |f=nf|.
@<Avoid reloading the font metrics, if identical font already present@>=
f:=font_map[f]; {it might already map elsewhere!}
if f=nf then {it's got to \&{be} a new font, of course!}
begin
f:=0; mismatch:=true;
while (f<nf) and mismatch do
begin
if font_scaled_size[f]=q then {don't try name unless same size}
@<Compare two font names, and set |mismatch| accordingly@>;
if mismatch then incr(f)
end; {|while f|}
if f<nf then {the font already exists, under a different identity}
font_map[nf]:=f;
end
@ If the \.{DVI} file has previously defined this font, we check that the
parameters of this |fnt_def| command match those previously read: there's
something amiss if they differ (and the user is supposed to determine what
by using \.{DVItype}).
@<Check that the current...@>=
begin if font_check_sum[f]<>c then
warning('---checksum doesn''t match previous definition!');
@:Warning: checksum doesn't match}{\quad\.{checksum doesn't match}@>
if font_scaled_size[f]<>q then
warning('---scaled size doesn''t match previous definition!');
@:Warning: scaled size doesn't match}{\quad\.{scaled size doesn't match}@>
if font_design_size[f]<>d then
warning('---design size doesn''t match previous definition!');
@:Warning: design size doesn't match}{\quad\.{design size doesn't match}@>
@<Compare two font names, and set |mismatch| accordingly@>;
if mismatch then warning('---font name doesn''t match previous definition!');
@:Warning: font name doesn't match}{\quad\.{font name doesn't match}@>
end
@ This little chunk of code compares the font names stored in
|names[font_name[f]..font_name[f+1]-1]| and
|names[font_name[nf]..font_name[nf+1]-1]|; if any character differs, or the
lengths of the names differ, then |mismatch| will be set.
@<Compare two font names, and set |mismatch| accordingly@>=
begin
j:=font_name[f]; k:=font_name[nf]; mismatch:=false;
while j<font_name[f+1] do
begin
if names[j]<>names[k] then mismatch:=true;
incr(j); incr(k);
end; {|while j|}
if k<>font_name[nf+1] then mismatch:=true
end
@ @<Read the font parameters into position for font |nf|...@>=
c:=signed_quad; font_check_sum[nf]:=c;@/
q:=signed_quad; font_scaled_size[nf]:=q;@/
d:=signed_quad; font_design_size[nf]:=d;@/
p:=get_byte; n:=get_byte;
if font_name[nf]+n+p>name_size then
capacity_exceeded('too many names (name size=',name_size:1,')');
@:capacity exceeded too many names}{\quad\.{too many names}@>
font_name[nf+1]:=font_name[nf]+n+p;
if not substitution_pass then
print('Font ',e:1,': ');
if n+p=0 then print('null font name!')
@.null font name@>
else for k:=font_name[nf] to font_name[nf+1]-1 do names[k]:=lower(get_byte);
incr(nf); if not substitution_pass then print_font(nf-1); decr(nf)
@ @<Load the new font, unless there are problems@>=
begin @<Move font name into the |cur_name| string@>;
open_tfm_file;
if (q<=0)or(q>=@'1000000000) then
abort('Font not loaded, bad scale (',q:1,')!')
@:fatal error Font not loaded bad scale}{\qquad\.{bad scale}@>
else if (d<=0)or(d>=@'1000000000) then
abort('Font not loaded, bad design size (',d:1,')!')
@:fatal error Font not loaded bad design size}{\qquad\.{bad design size}@>
else if in_TFM(q) then @<Finish loading the new font info@>;
end
@ Strictly speaking, we should derive the width of a `thin space' from
|param[6]| in the |tfm_file|, but this program (in common with such paragons
of virtue as \.{DVItype}!) doesn't bother to read that far in the file; so
we assume that \.{1em} in the font is equivalent to the design size, and
knowing that an \.{em} in the Computer Modern fonts is \.{18u\#} wide, take
one-sixth of this to represent the `thin space'.
@<Finish loading...@>=
begin font_space[nf]:=q div 6; {this is a 3-unit ``thin space''}
if (c<>0)and(tfm_check_sum<>0)and(c<>tfm_check_sum) then
warning('checksum doesn''t match; DVI=',c:1,' vs. TFM=',tfm_check_sum:1);
@:Warning: checksum doesn\'t match}{\quad\.{checksum doesn't match}@>
print('---loaded at size ',q:1,' DVI units');
d:=round((100.0*conv*q)/(true_conv*d));
if d<>100 then
print(' (this font is magnified ',d:1,'%)');
@.this font is magnified@>
incr(nf); {now the new font is officially present}
font_space[nf]:=0; {for |out_space| and |out_vmove|}
end
@ The string |cur_name| is supposed to be set to the external name of the
\.{TFM} file for the current font. This usually means that we need to
prepend the name of the default directory, and
to append the suffix `\.{.TFM}'. Furthermore, we change lower case letters
to upper case, since |cur_name| is a \PASCAL\ string.
If |p=0|, i.e., if no font directory has been specified, \.{DVItoLN03}
is supposed to use the default font directory, which is a
system-dependent place where the standard fonts are kept; in the current
implementation, this will have been read into |tfm_directory| from the
command-line qualifier \.{/font\UL directory}.
@^system dependencies@>
@<Move font name into the |cur_name| string@>=
for k:=1 to name_length do cur_name[k]:=' ';
if p=0 then
begin for k:=1 to tfm_directory.length do
cur_name[k]:=tfm_directory.body[k];
r:=tfm_directory.length;
end
else r:=0;
for k:=font_name[nf] to font_name[nf+1]-1 do
begin incr(r);
if r+4>name_length then
capacity_exceeded('font file name too long (max length=',name_length:1,')');
@:capacity exceeded font file name}{\quad\.{font file name too long}@>
if (names[k]>="a")and(names[k]<="z") then
cur_name[r]:=xchr[names[k]-@'40]
else cur_name[r]:=xchr[names[k]];
end;
cur_name[r+1]:='.'; cur_name[r+2]:='T'; cur_name[r+3]:='F'; cur_name[r+4]:='M'
@* Accumulation of page. We save a page before outputting it, to minimize the
complexity of the page for the LN03.
The page is stored as a series of linked lists of ``segments'', with
(potentially) one such list being stored for each pixel position in the
vertical direction.
The individual segments are a structure, containing the $x$-coordinates of the
start and finish of the text. We also record the current font in which the
text is to be set, and a pointer to the next segment in the list.
We therefore declare here the structure to hold the segment, and an array of
pointers, one for each vertical pixel position. We also provide a further two
segment pointers, one for the ``current'' segment, and another pointing to a
list of allocated, but otherwise unused, segments. Thus it is only necessary
to call for storage allocation when the free list is exhausted.
If a glyph proved to have too large a bitmap for it to have been downloaded
as a character into the LN03, a segment was appended which references that
glyph alone; this is necessary to ensure that the bitmaps are only output
once the |eop| has been met, to accommodate changes of orientation brought
about by \.{\\special} commands.
@d max_text=max_print {Largest text segment held in a record}
@<Types in the...@>=
@!string = varying [10] of char;
{to hold character representations of integers}
@!segment = packed record
@!next : ^segment; {pointer to next segment, or |nil|}
@!xs,@!xe : integer; {$x$-coords of start and finish of text}
@!font : integer; {LN03 downloaded font \#; external font for bitmaps}
@!ch : eight_bits; {character from LN03 internal font}
@!seg_type : (@!glyph_string,@!bitmap,@!sixel_dump);
@!direction : page_orientation; {|landscape| or |portrait|}
@!text : varying [max_text] of char; {sequence for output to printer}
end;
@ There is one |seg_list| pointer for each possible horizontal row of pixels
on the paper; |cur_seg| points to the current segment, whilst |free_list_seg|
maintains a list of free segments. Any characters which would appear outside
the bounds of the paper are accumulated in segments of the |lost_seg| list.
@<Globals in the ...@>=
@!seg_list : array [y_land_min..paper_ht+y_port_min] of ^segment;
@!cur_seg, @!free_seg_list : ^segment;
@!lost_seg : ^segment; {For segments set off the paper}
@!seg_max : integer; {could be used for statistics}
@ @<Set initial...@>=
free_seg_list:=nil; cur_seg:=nil; lost_seg:=nil;
for k:=y_land_min to paper_ht+y_port_min do seg_list[k]:=nil;
seg_max:=0;
top_of_page:=true;
@ The procedure |new_segment| allocates storage for a new segment. The
program maintains a |free_seg_list| to which processed segments are returned;
however, if no free segments remain on this list (as will obtain on first use
of the program), the \PASCAL\ |new| function is used to allocate an
appropriate area.
After the segment has been allocated, as many fields as possible are
completed: (1) the |next| field points to the existing |seg_list| to which
this new segment now belongs; (2) the |font| field records the font currently
selected; (3) the |seg_list| is made to point to this new segment. If the
text is known to be invalid (for the case in which it has been discovered that
the text will not fit on the physical paper), then the new segment is added
instead to the |lost_seg| list, and the |font| field is used to record the
$y$-position at which \TeX\ had tried to locate the text.
@p procedure new_segment(@!valid,@!offset:boolean;
@!font_code,@!ch_code:integer);
begin
@<Find or create a new |cur_seg|@> ;
@<Set fields of |cur_seg|@>
end; {|new_segment|}
@ If the |free_seg_list| is empty, either we've never allocated any segments
before, or we've used them all. We therefore use the standard \PASCAL\
allocator function, |new|.
Alternatively, we just detach the first segment from the |free_seg_list|.
@<Find or create a new |cur_seg|@>=
if free_seg_list = nil then
begin new(cur_seg);
incr(seg_max)
end else
begin
cur_seg:=free_seg_list;
free_seg_list:=cur_seg^.next
end
@ If the character being set is correctly within the imaging area of the
paper, we add the new segment to the segment list associated with its vertical
position on the page, and record the |ln_font| in which the character shall be
output.
Alternatively, we add the segment to the list of |lost_seg|s, and use the
|font| field to record the vertical |y_pos| at which \TeX\ \\{thought} it
was imaging the text.
References to actual paper positions are always offset by the setting of
|left_marg| and |top_marg|.
@d x_pos==hh+left_marg {Actual paper coordinates}
@d y_pos==vv+top_marg
@<Set fields of |cur_seg|@>=
with cur_seg^ do
if valid then
begin
ch:=ch_code;
direction:=new_orient;
if offset then {oversized glyphs and sixel graphics inclusions}
begin
next:=seg_list[y_pos+bitmap_offset]; {Goes on `lower' list}
seg_type:=bitmap; {this is corrected externally if necessary}
font:=cur_font; {record index into fonts for later file opening}
seg_list[y_pos+bitmap_offset]:=cur_seg {Put at front of the list}
end
else
begin
next:=seg_list[y_pos];
seg_type:=glyph_string;
font:=font_code; {record number of a \\{downloaded} font}
seg_list[y_pos]:=cur_seg {Put at front of the list}
end
end else
begin next:=lost_seg; {Don't put it on paper}
font:=y_pos; {Remember where \TeX\ wanted to put it}
lost_seg:=cur_seg
end
@ Procedure |finish_seg| is used to discard segments which record text which
was set off the paper, after making appropriate error indications in the log
(\.{TYP}) file. Discarded segments are returned to the |free_seg_list|.
The procedure is also called when a glyph is met that cannot become a member
of that held in |cur_seg|; this just results in |cur_seg:=nil|. The segment
is already attached to some part of |seg_list| array, so nothing further is
required, and nothing gets lost!
@p procedure finish_seg;
begin
while (cur_seg<>nil) and (cur_seg=lost_seg) do
begin {We've got something set off the paper}
with cur_seg^ do
begin
warning('Characters ''',text,''' set off paper ');
@:Warning: Characters set off paper}{\quad\.{Characters ... set off paper}@>
write_ln(term_out,'(see log file)',crlf);
print_ln('( x = ',xs:1,'..',xe:1,', y = ',font:1,' ) - ignored');
lost_seg:=next; next:=free_seg_list; free_seg_list:=cur_seg;
cur_seg:=lost_seg
end;
end;
cur_seg:=nil {Forget that we've got a current segment}
end;
@ The function |str_int| yields a minimum length ASCII representation for its
integer parameter |num|. It does this by invoking the VAX-\PASCAL\ specific
function |dec(n,f,d)|, which yields an |f| character-long string containing
the decimal representation of the integer |n| occupying |d| digits at the
right-hand end of the string; if necessary leading `\.0' characters are
inserted to ensure that |d| digits are printed. (The leading |f-d| characters
of the string would be filled with the space character `\.\SP', but we
ensure that |f| and |d| are both just wide enough to avoid such a problem!)
@d VAX_decimal==@=dec@>
@p function str_int(num : integer) : string;
var field,digits : integer;
begin if num = 0 then digits:=1 else
digits:=trunc(ln(abs(num))/ln(10))+1;
{$\bigl\lceil\log_{10}\vert\hbox{\\{num}}\vert\bigr\rceil$ is number of digits
required}
field:=digits;
if num < 0 then incr(field);
str_int:=VAX_decimal(num,field,digits)
end;
@ We define here some non-printing control characters, which will later be
required for control and escape sequences.
@d LF==chr(@"0A)
@d FF==chr(@"0C)
@d esc==chr(@"1B) {The ASCII ESCape character}
@d csi==chr(@"9B) {Control Sequence Introducer}
@d dcs==chr(@"90) {Device Control String}
@d st==chr(@"9C) {String Terminator}
@ The following |move_forward| procedure includes within the text of a segment
the appropriate LN03 |HPR| (Horizontal Position Relative) control sequence to
instruct the printer to move the printing position to the right. This
sequence consists of the control sequence introducer, |csi|, followed by the
motion expressed in pixels, followed by the sequence terminator `\.a'.
@p procedure move_forward(dist:integer);
begin
cur_seg^.text:=cur_seg^.text + csi+str_int(dist)+'a';
end;
@ Because of the restrictions of \PASCAL, we have to make a |forward|
declaration of procedure |do_page|. The procedure is called (recursively)
to interpret the |dvi| bytes stored from a character packet in a virtual
font. It also implements the normal operations for interpreting the
contents of a page read from the \.{DVI} file.
@p function do_page(@!vf_start:integer):boolean; forward;
@ Procedure |ord_text| adds a single character, |ch|, taken from the
|cur_font|, to a segment preparatory to its being ``set'' on the paper. The
procedure cannot be declared at this point in the \.{WEB} source because it
references a number of procedures that haven't yet been declared. However,
we'll arrange for it to appear in the correct part of the \PASCAL\ later on.
If the character is taken from a virtual font, and requires more than the
simple operation of setting a single character from a single font, then this
procedure calls |do_page| recursively to interpret the sequence of |dvi|
bytes that have been stored into |vf| from the \.{VF} file.
This macro is equivalent to |glyph_map(cur_font)(#)|, assuming that
|cur_base=glyph_base[cur_font]|.
@d cur_font_glyph(#)==glyphs[cur_base+#]
@<Declare the |ord_text| Procedure@>=
procedure ord_text(ch:integer);
var @!good : boolean;
begin
with cur_font_glyph(ch) do
begin
@<Check if glyph appears within printable area@>;
@<Allocate segment to hold text string@>;
if loaded=virtual then
begin
finish_seg; {Whatever precedes this character to be treated
separately}
if not do_page(seq_off) then
abort('VF packet failed [',cur_font:1,',',ch:1,']!');
@:fatal error VF packet failed}{\quad\.{VF packet failed}@>
finish_seg; {Don't run into next character or record this one's
width twice}
end
else begin
@<Ensure entered text does not overflow |cur_seg|@>;
@<Set character onto page@>
end
end
end {|procedure ord_text|}
@ If the character would be set off the physical paper addressable by the LN03
printer, the segment belongs to the |lost_seg| list.
@<Check if glyph appears within printable area@>=
if (x_pos<x_min) or (x_pos>page_wid) or (y_pos<y_min) or (y_pos>page_len) then
begin
good := false;
if (cur_seg<>nil) then
begin
if (cur_seg<>lost_seg) then
finish_seg {earlier glyphs OK, even though this is off paper}
else
if cur_seg^.font<>y_pos then finish_seg {these were off paper, but on
a different row}
end;
end else@/
begin {The character fits on the paper}
good := true;
if cur_seg<>nil then
begin
if cur_seg=lost_seg then finish_seg {report earlier off-paper chars}
else
if cur_seg^.seg_type=bitmap then finish_seg; {previous segment contains
single oversized glyph}
if loaded=no then finish_seg {this segment will be ditto}
end
end
@ If there is no |cur_seg| to hold the text, one is allocated, and the start and
finish coordinates of the text therein recorded. The |text| field is set to be
the null string.
@<Allocate segment to hold text string@>=
if cur_seg = nil then
begin new_segment(good,loaded=no,font_code,ch);
cur_seg^.xs:=x_pos;
cur_seg^.xe:=x_pos;
cur_seg^.text:=''
end
@ Checks are made that this segment (1) would not produce too large a
contiguous sequence of characters (which might therefore lead to an overlong
output record); (2) that the character does not need to be positioned to the
\&{left} of those already in the segment; (3) that the LN03 font used for this
character is the same as that for the other characters of the segment. If any
of these checks fail, we commence a new segment; these tests are bypassed if
we're setting a single oversized glyph.
If any additional rightward movement is needed, the appropriate escape sequence
is added to the |text| in the |cur_seg|.
@<Ensure entered text does not overflow |cur_seg|@>=
if cur_seg^.seg_type=glyph_string then
begin
if (VAX_length(cur_seg^.text)>=max_text-8) or
(x_pos<cur_seg^.xe) or
(font_code<>cur_seg^.font) then
begin finish_seg;
@<Allocate segment...@> {tests enumerated above failed}
end
else
if x_pos>cur_seg^.xe then {We need to move rightward}
move_forward(x_pos-cur_seg^.xe) {Add character to segment}
end
@ Characters whose glyphs proved too large to download in an LN03 font are
saved up to be printed as a pixel dump; other glyphs are recorded in the
current segment for imaging through the downloaded fonts.
If the character being stored is only being recorded to support generation
of an error message, it is translated back from ASCII to the external
representation for printing of the message in the |type_file|.
@<Set character onto page@>=
if good then
with cur_seg^ do
case loaded of
yes:
text:=text+chr(char_code);
no, missing:
finish_seg; {needn't consider further}
othercases
do_nothing;
endcases
else
cur_seg^.text:=cur_seg^.text+xchr[ch]
@* Packed file format.
This format was designed by Tomas Rokicki in August, 1985.
@^Rokicki, Tomas@>
It constitutes a compact representation of the data contained in a
\.{GF} file. The information content is the same, but packed (\.{PK}) files
are almost always less than half the size of their \.{GF} counterparts. They
are also easier to convert into a raster representation because they do not
have a profusion of \\{paint}, \\{skip}, and \\{new\_row} commands to be
separately interpreted. In addition, the \.{PK} format expressedly forbids
\&{special} commands within a character. The minimum bounding box for each
character is explicit in the format, and does not need to be scanned for as in
the \.{GF} format. Finally, the width and escapement values are combined with
the raster information into character ``packets'', making it simpler in many
cases to process a character.
A \.{PK} file is organized as a stream of 8-bit bytes. At times, these bytes
might be split into 4-bit nybbles or single bits, or combined into multiple
byte parameters. When bytes are split into smaller pieces, the `first' piece
is always the most significant of the byte. For instance, the first bit of
a byte is the bit with value 128; the first nybble can be found by dividing
a byte by 16. Similarly, when bytes are combined into multiple byte
parameters, the first byte is the most significant of the parameter. If the
parameter is signed, it is represented by two's-complement notation.
The set of possible eight-bit values are separated into two sets, those that
introduce a character definition, and those that do not. The values that
introduce a character definition comprise the range from 0 to 239; byte values
above 239 are interpreted commands. Bytes which introduce character
definitions are called flag bytes, and various fields within the byte indicate
various things about how the character definition is encoded. Command bytes
have zero or more parameters, and can never appear within a character
definition or between parameters of another command, where they would be
interpeted as data.
A \.{PK} file consists of a preamble, followed by a sequence of one or more
character definitions, followed by a postamble. The preamble command must
be the first byte in the file, followed immediately by its parameters.
Any number of character definitions may follow, and any command but the
preamble command and the postamble command may occur between character
definitions. The very last command in the file must be the postamble.
@ The packed file format is intended to be easy to read and interpret by
device drivers. The small size of the file reduces the input/output overhead
each time a font is defined. For those drivers that load and save each font
file into memory, the small size also helps reduce the memory requirements.
The length of each character packet is specified, allowing the character raster
data to be loaded into memory by simply counting bytes, rather than
interpreting each command; then, each character can be interpreted on a demand
basis. This also makes it possible for a driver to skip a particular
character quickly if it knows that the character is unused.
@ First, the command bytes shall be presented; then the format of the
Character definitions will be defined. Eight of the possible sixteen
commands (values 240 through 255) are currently defined; the others are
reserved for future extensions. The commands are listed below. Each command
is specified by its symbolic name (e.g., \\{pk\_no\_op}), its opcode byte,
and any parameters. The parameters are followed by a bracketed number
telling how many bytes they occupy, with the number preceded by a plus sign if
it is a signed quantity. (Four byte quantities are always signed, however.)
\yskip\hang|pk_xxx1| 240 |k[1]| |x[k]|. This command is undefined in general;
it functions as a $(k+2)$-byte \\{no\_op} unless special \.{PK}-reading
programs are being used. \MF\ generates \\{xxx} commands when encountering
a \&{special} string. It is recommended that |x| be a string having the form
of a keyword followed by possible parameters relevant to that keyword.
\yskip\hang\\{pk\_xxx2} 241 |k[2]| |x[k]|. Like |pk_xxx1|, but |0<=k<65536|.
\yskip\hang\\{pk\_xxx3} 242 |k[3]| |x[k]|. Like |pk_xxx1|, but
|0<=k<@t$2^{24}$@>|. \MF\ uses this when sending a \&{special} string whose
length exceeds~255.
\yskip\hang\\{pk\_xxx4} 243 |k[4]| |x[k]|. Like |pk_xxx1|, but |k| can be
ridiculously large; |k| musn't be negative.
\yskip\hang|pk_yyy| 244 |y[4]|. This command is undefined in general; it
functions as a five-byte \\{no\_op} unless special \.{PK} reading programs
are being used. \MF\ puts |scaled| numbers into |yyy|'s, as a result of
\&{numspecial} commands; the intent is to provide numeric parameters to
\\{xxx} commands that immediately precede.
\yskip\hang|pk_post| 245. Beginning of the postamble. This command is
followed by enough |pk_no_op| commands to make the file a multiple
of four bytes long. Zero through three bytes are usual, but any number
is allowed.
This should make the file easy to read on machines which pack four bytes to
a word.
\yskip\hang|pk_no_op| 246. No operation, do nothing. Any number of
|pk_no_op|'s may appear between \.{PK} commands, but a |pk_no_op| cannot be
inserted between a command and its parameters, between two parameters, or
inside a character definition.
\yskip\hang|pk_pre| 247 |i[1]| |k[1]| |x[k]| |ds[4]| |cs[4]| |hppp[4]|
|vppp[4]|. Preamble command. Here, |i| is the identification byte of the
file, currently equal to 89. The string |x| is merely a comment, usually
indicating the source of the \.{PK} file. The parameters |ds| and |cs| are
the design size of the file in $1/2^{20}$ points, and the checksum of the
file, respectively. The checksum should match the \.{TFM} file and the
\.{GF} files for this font. Parameters |hppp| and |vppp| are the ratios
of pixels per point, horizontally and vertically, multiplied by $2^{16}$; they
can be used to correlate the font with specific device resolutions,
magnifications, and ``at sizes''. Usually, the name of the \.{PK} file is
formed by concatenating the font name (e.g., amr10) with the resolution at
which the font is prepared in pixels per inch multiplied by the magnification
factor, and the letters \.{PK}. For instance, amr10 at 300 dots per inch
should be named AMR10.300PK; at one thousand dots per inch and magstephalf,
it should be named AMR10.1095PK.
@ We put a few of the above opcodes into definitions for symbolic use by
this program.
@d pk_id = 89 {the version of \.{PK} file described}
@d pk_xxx1 = 240 {\&{special} commands}
@d pk_yyy = 244 {\&{numspecial} commands}
@d pk_post = 245 {postamble}
@d pk_no_op = 246 {no operation}
@d pk_pre = 247 {preamble}
@ The \.{PK} format has two conflicting goals; to pack character raster and
size information as compactly as possible, while retaining ease of translation
into raster and other forms. A suitable compromise was found in the use of
run-encoding of the raster information. Instead of packing the individual
bits of the character, we instead count the number of consecutive `black' or
`white' pixels in a horizontal raster row, and then encode this number. Run
counts are found for each row, from the top of the character to the bottom.
This is essentially the way the \.{GF} format works.
Instead of presenting each row individually, however, let us concatenate all
of the horizontal raster rows into one long string of pixels, and encode this
row. With knowledge of the width of the bit-map, the original character glyph
can be easily reconstructed. In addition, we do not need special commands to
mark the end of one row and the beginning of the next.
Next, let us put the burden of finding the minimum bounding box on the part
of the font generator, since the characters will usually be used much more
often than they are generated. The minimum bounding box is the smallest
rectangle which encloses all `black' pixels of a character. Let us also
eliminate the need for a special end of character marker, by supplying
exactly as many bits as are required to fill the minimum bounding box, from
which the end of the character is implicit.
Let us next consider the distribution of the run counts. Analysis of several
dozen pixel files at 300 dots per inch yields a distribution peaking at four,
falling off slowly until ten, then a bit more steeply until twenty, and then
asymptotically approaching the horizontal. Thus, the great majority of our
run counts will fit in a four-bit nybble. The eight-bit byte is attractive for
our run-counts, as it is the standard on many systems; however, the wasted four
bits in the majority of cases seems a high price to pay. Another possibility
is to use a Huffman-type encoding scheme with a variable number of bits for
each run-count; this was rejected because of the overhead in fetching and
examining individual bits in the file. Thus, the character raster definitions
in the \.{PK} file format are based on the four-bit nybble.
@ The analysis of the pixel files yielded another interesting statistic: fully
37\char`\%\
of the raster rows were duplicates of the previous row. Thus, the \.{PK}
format allows the specification of repeat counts, which indicate how many times
a horizontal raster row is to be repeated. These repeated rows are taken out
of the character glyph before individual rows are concatenated into the long
string of pixels.
For elegance, we disallow a run count of zero. The case of a null raster
description should be gleaned from the character width and height being equal
to zero, and no raster data should be read. No other zero counts are ever
necessary. Also, in the absence of repeat counts, the repeat value is set to
be zero (only the original row is sent.) If a repeat count is seen, it takes
effect on the current row. The current row is defined as the row on which the
first pixel of the next run count will lie. The repeat count is set back to
zero when the last pixel in the current row is seen, and the row is sent out.
This poses a problem for entirely black and entirely white rows, however. Let
us say that the current row ends with four white pixels, and then we have five
entirely empty rows, followed by a black pixel at the beginning of the next
row, and the character width is ten pixels. We would like to use a repeat
count, but there is no legal place to put it. If we put it before the white
run count, it will apply to the current row. If we put it after, it applies
to the row with the black pixel at the beginning. Thus, entirely white or
entirely black repeated rows are always packed as large run counts (in this
case, a white run count of 54) rather than repeat counts.
@ Now let us turn our attention to the actual packing of the run counts and
repeat counts into nybbles. There are only sixteen possible nybble values.
We need to indicate run counts and repeat counts. Since the run counts are
much more common, we will devote the majority of the nybble values to them.
We therefore indicate a repeat count by a nybble of 14 followed by a packed
number, where a packed number will be explained later. Since the repeat
count value of one is so common, we indicate a repeat one command by a single
nybble of 15. A 14 followed by the packed number 1 is still legal for a
repeat one count, however. The run counts are coded directly as packed
numbers.
For packed numbers, therefore, we have the nybble values 0 through 13. We
need to represent the positive integers up to, say, $2^{31}-1$. We would
like the more common smaller numbers to take only one or two nybbles, and
the infrequent large numbers to take three or more. We could therefore
allocate one nybble value to indicate a large run count taking three or more
nybbles. We do this with the value 0.
@ We are left with the values 1 through 13. We can allocate some of these, say
|dyn_f|, to be one-nybble run counts.
These will work for the run counts |1..dyn_f|. For subsequent run
counts, we will use a nybble greater than |dyn_f|, followed by a second nybble,
whose value can run from 0 through 15. Thus, the two-byte nybble values will
run from |dyn_f+1..(13-dyn_f)*16+dyn_f|. We have our definition of large run
count values now, being all counts greater than |(13-dyn_f)*16+dyn_f|.
We can analyze our several dozen pixel files and determine an optimal value of
|dyn_f|, and use this value for all of the characters. Unfortunately, values
of |dyn_f| that pack small characters well tend to pack the large characters
poorly, and values that pack large characters well are not efficient for the
smaller characters. Thus, we choose the optimal |dyn_f| on a character basis,
picking the value which will pack each individual character in the smallest
number of nybbles. Legal values of |dyn_f| run from 0 (with no one-byte run
counts) to 13 (with no two-byte run counts).
@ Our only remaining task in the coding of packed numbers is the large run
counts. We use a scheme suggested by D.~E.~Knuth
@^Knuth, D.~E.@>
which will simply and elegantly represent arbitrarily large values. The
general scheme to represent an integer |i| is to write its hexadecimal
representation, with leading zeros removed. Then we count the number of
digits, and prepend one less than that many zeros before the hexadecimal
representation. Thus, the values from one to fifteen occupy one nybble;
the values sixteen through 255 occupy three, the values 256 through 4095
require five, etc.
For our purposes, however, we have already represented the numbers one
through |(13-dyn_f)*16+dyn_f|. In addition, the one-nybble values have
already been taken by our other commands, which means that only the values
from sixteen up are available to us for long run counts. Thus, we simply
normalize our long run counts, by subtracting |(13-dyn_f)*16+dyn_f+1| and
adding 16, and then representing the result according to the scheme above.
@ The final algorithm for decoding the run counts based on the above scheme
might look like this, assuming a procedure called \\{pk\_nyb} is available
to get the next nybble from the file, and assuming that the global
|repeat_count| indicates whether a row needs to be repeated. Note that this
routine is recursive, but since a repeat count can never directly follow
another repeat count, it can only be recursive to one level.
@<Packed number procedure@>=
function pk_packed_num : integer ;
var i, j, k : integer ;
begin
i := get_nyb ;
if i = 0 then begin
repeat j := get_nyb ; incr(i) ; until j <> 0 ;
while i > 0 do begin j := j * 16 + get_nyb ; decr(i) ; end ;
pk_packed_num := j - 15 + (13-dyn_f)*16 + dyn_f ;
end else if i <= dyn_f then
pk_packed_num := i
else if i < 14 then
pk_packed_num := (i-dyn_f-1)*16+get_nyb+dyn_f+1
else begin
if i = 14 then
repeat_count := pk_packed_num
else
repeat_count := 1 ;
pk_packed_num := pk_packed_num ;
end ;
end ;
@ For low resolution fonts, or characters with `gray' areas, run encoding can
often make the character many times larger. Therefore, for those characters
that cannot be encoded efficiently with run counts, the \.{PK} format allows
bit-mapping of the characters. This is indicated by a |dyn_f| value of
14. The bits are packed tightly, by concatenating all of the horizontal raster
rows into one long string, and then packing this string eight bits to a byte.
The number of bytes required can be calculated by |(width*height+7) div 8|.
This format should only be used when packing the character by run counts takes
more bytes than this, although, of course, it is legal for any character.
Any extra bits in the last byte should be set to zero.
@ At this point, we are ready to introduce the format for a character
descripter. It consists of three parts: a flag byte, a character preamble,
and the raster data. The most significant four nybbles of the flag byte
yield the |dyn_f| value for that character. (Notice that only values of
0 through 14 are legal for |dyn_f|, with 14 indicating a bit mapped character;
thus, the flag bytes do not conflict with the command bytes, whose upper nybble
is always 15.) The next bit (with weight 16) indicates whether the first run
count is a black count or a white count, with a one indicating a black count.
For bit-mapped characters, this bit should be set to a zero. The next bit
(with weight 8) indicates whether certain later parameters (referred to as size
parameters) are given in one-byte or two-byte quantities, with a one indicating
that they are in two-byte quantities. The last two bits are concatenated on to
the beginning of the length parameter in the character preamble, which will be
explained below.
However, if the last three bits of the flag byte are all set (normally
indicating that the size parameters are two-byte values and that a 3 should be
prepended to the length parameter), then a long format of the character
preamble should be used instead of one of the short forms.
Therefore, there are three formats for the character preamble, and which one
is used depends on the least significant three bits of the flag byte. If the
least significant three bits are in the range zero through three, the short
format is used. If they are in the range four through six, the extended short
format is used. Otherwise, if the least significant bits are all set, then
the long form of the character preamble is used. The preamble formats are
explained below.
\yskip\hang Short form: |flag[1]| |pl[1]| |cc[1]| |tfm[3]| |dm[1]| |w[1]|
|h[1]| |hoff[+1]| |voff[+1]|.
If this format of the character preamble is used, the above
parameters must all fit in the indicated number of bytes, signed or unsigned
as indicated. Almost all of the standard \TeX\ font characters fit; the few
exceptions are fonts such as \.{aminch}.
\yskip\hang Extended short form: |flag[1]| |pl[2]| |cc[1]| |tfm[3]| |dm[2]|
|w[2]| |h[2]| |hoff[+2]| |voff[+2]|. Larger characters use this extended
format.
\yskip\hang Long form: |flag[1]| |pl[4]| |cc[4]| |tfm[4]| |dx[4]| |dy[4]|
|w[4]| |h[4]| |hoff[4]| |voff[4]|. This is the general format which
allows all of the
parameters of the \.{GF} file format, including vertical escapement.
\vskip\baselineskip
The |flag| parameter is the flag byte. The parameter |pl| (packet length)
contains the offset
of the byte following this character descripter, with respect to the beginning
of the |tfm| width parameter. This is given so a \.{PK} reading program can,
once it has read the flag byte, packet length, and character code (|cc|), skip
over the character by simply reading this many more bytes. For the two short
forms of the character preamble, the last two bits of the flag byte should be
considered the two most-significant bits of the packet length. For the short
format, the true packet length might be calculated as |(flag mod 4)*256+pl|;
for the extended format, it might be calculated as |(flag mod 4)*65536+pl|.
The |w| parameter is the width and the |h| parameter is the height in pixels
of the minimum bounding box. The |dx| and |dy| parameters are the horizontal
and vertical escapements, respectively. In the short formats, |dy| is assumed
to be zero and |dm| is |dy| but in pixels;
in the long format, |dx| and |dy| are both
in pixels multiplied by $2^{16}$. The |hoff| is the horizontal offset from the
upper left pixel to the reference pixel; the |voff| is the vertical offset.
They are both given in pixels, with right and down being positive. The
reference pixel is the pixel which occupies the unit square in \MF; the
\MF\ reference point is the lower left hand corner of this pixel. (See the
example below.)
@ \TeX\ requires that all characters which have the same character codes
modulo 256 also have the same |tfm| widths, and escapement values. The \.{PK}
format does not itself make this a requirement, but in order for the font to
work correctly with the \TeX\ software, this constraint should be observed.
The current version of \TeX\ (1.5) cannot output character codes greater
than 255 anyway.
Following the character preamble is the raster information for the
character, packed by run counts or by bits, as indicated by the flag byte.
If the character is packed by run counts and the required number of nybbles
is odd, then the last byte of the raster description should have a zero
for its least significant nybble.
@ As an illustration of the \.{PK} format, the character \char4\ from the font
amr10 at 300 dots per inch will be encoded. This character was chosen
because it illustrates some
of the borderline cases. The raster for the character looks like this (the
row numbers are chosen for convenience, and are not \MF's row numbers.)
\vskip\baselineskip
\centerline{\vbox{\baselineskip=10pt
\halign{\hfil#\quad&&\hfil#\hfil\cr
0& & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M\cr
1& & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M\cr
2& & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M\cr
3& & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M\cr
4& & &M&M& & & & & & & & & & & & & & & & &M&M\cr
5& & &M&M& & & & & & & & & & & & & & & & &M&M\cr
6& & &M&M& & & & & & & & & & & & & & & & &M&M\cr
7\cr
8\cr
9& & & & &M&M& & & & & & & & & & & & &M&M& & \cr
10& & & & &M&M& & & & & & & & & & & & &M&M& & \cr
11& & & & &M&M& & & & & & & & & & & & &M&M& & \cr
12& & & & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M& & \cr
13& & & & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M& & \cr
14& & & & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M& & \cr
15& & & & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M& & \cr
16& & & & &M&M& & & & & & & & & & & & &M&M& & \cr
17& & & & &M&M& & & & & & & & & & & & &M&M& & \cr
18& & & & &M&M& & & & & & & & & & & & &M&M& & \cr
19\cr
20\cr
21\cr
22& & &M&M& & & & & & & & & & & & & & & & &M&M\cr
23& & &M&M& & & & & & & & & & & & & & & & &M&M\cr
24& & &M&M& & & & & & & & & & & & & & & & &M&M\cr
25& & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M\cr
26& & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M\cr
27& & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M\cr
28&*& &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M\cr
&\hphantom{M}&\hphantom{M}\cr
}}}
The width of the minimum bounding box for this character is 20; its height
is 29. The `*' represents the reference pixel; notice how it lies outside the
minimum bounding box. The |hoff| value is $-2$, and the |voff| is~28.
The first task is to calculate the run counts and repeat counts. The repeat
counts are placed at the first transition (black to white or white to black)
in a row, and are enclosed in brackets. White counts are enclosed in
parentheses. It is relatively easy to generate the counts list:
\vskip\baselineskip
\centerline{82 [2] (16) 2 (42) [2] 2 (12) 2 (4) [3]}
\centerline{16 (4) [2] 2 (12) 2 (62) [2] 2 (16) 82}
\vskip\baselineskip
Note that any duplicated rows that are not all white or all black are removed
before the repeat counts are calculated. The rows thus removed are rows 5, 6,
10, 11, 13, 14, 15, 17, 18, 23, and 24.
@ The next step in the encoding of this character is to calculate the optimal
value of |dyn_f|. The details of how this calculation is done are not
important here; suffice it to say that there is a simple algorithm which in one
pass over the count list can determine the best value of |dyn_f|. For this
character, the optimal value turns out to be 8 (atypically low). Thus, all
count values less than or equal to 8 are packed in one nybble; those from
nine to $(13-8)*16+8$ or 88 are packed in two nybbles. The run encoded values
now become (in hex, separated according to the above list):
\vskip\baselineskip
\centerline{\tt D9 E2 97 2 B1 E2 2 93 2 4 E3}
\centerline{\tt 97 4 E2 2 93 2 C5 E2 2 97 D9}
\vskip\baselineskip\noindent
which comes to 36 nybbles, or 18 bytes. This is shorter than the 73 bytes
required for the bit map, so we use the run count packing.
@ The short form of the character preamble is used because all of the
parameters fit in their respective lengths. The packet length is therefore
18 bytes for the raster, plus
eight bytes for the character preamble parameters following the character
code, or 26. The |tfm| width for this character is 640796, or {\tt 9C71C} in
hexadecimal. The horizontal escapement is 25 pixels. The flag byte is
88 hex, indicating the short preamble, the black first count, and the
|dyn_f| value of 8. The final total character packet, in hexadecimal, is:
\vskip\baselineskip
$$\vbox{\halign{\hfil #\quad&&{\tt #\ }\cr
Flag byte&88\cr
Packet length&1A\cr
Character code&04\cr
|tfm| width&09&C7&1C\cr
Horizontal escapement (pixels)&19\cr
Width of bit map&14\cr
Height of bit map&1D\cr
Horizontal offset (signed)&FE\cr
Vertical offset&1C\cr
Raster data&D9&E2&97\cr
&2B&1E&22\cr
&93&24&E3\cr
&97&4E&22\cr
&93&2C&5E\cr
&22&97&D9\cr}}$$
@* Routines to access the packed pixel files. We shall require routines to
access the packed file; sometimes this may even require access to individual
\&{bits}, but mostly we shall be accessing bytes or nybbles.
We also need a function that will get a single byte from the \.{PK} file.
Again, buffering may be done in this procedure.
@p function pk_byte : eight_bits ;
var temp : eight_bits ;
begin
temp := pxl_file^[pk_loc mod VAX_block_length] ;
pk_loc := pk_loc + 1 ;
if (pk_loc mod VAX_block_length)=0 then
get(pxl_file) ;
pk_byte := temp ;
end ;
@ As we are reading the packed file, we often need to fetch (signed or
unsigned) 16 and 32 bit quantities. Here we have three procedures to do this.
@p function get_16 : integer ;
var a : integer ;
begin a := pk_byte ; get_16 := a * 256 + pk_byte ; end ;
@#
function get_16_signed : integer ;
var a : integer ;
begin a := get_16; if a>32767 then a := a-65536;
get_16_signed := a ; end ;
@#
function get_32 : integer ;
var a : integer ;
begin a := get_16 ; if a > 32767 then a := a - 65536 ;
get_32 := a * 65536 + get_16 ; end ;
@ Now we read and check the preamble of the pk file. In the preamble, we
find the |hppp|, |dsize|, |checksum|. After we've read the preamble, no
character locators will be available.
@d two_to_the_20th==@"100000
@<Read preamble@>=
pk_loc:=0; char_num:=-1; {Ensure that |char_num| is ``undefined''}
if pk_byte <> pk_pre then abort('Bad pk file, pre command missing') ;
@:fatal error Bad pk file ...}{\quad\.{Bad pk file}@>
@:fatal error Bad pk file pre command missing}{\qquad\\{pre }\.{command missing}@>
if pk_byte <> pk_id then abort('Bad pk file, wrong version') ;
@:fatal error Bad pk file wrong version}{\qquad\.{wrong version}@>
pk_j := pk_byte ;
for pk_i := 1 to pk_j do dummy:=pk_byte ; { Skip over PK file comment }
dsize := get_32 ;
checksum := get_32 ;
hppp := get_32 ; vppp := get_32 ;
magnification := round(hppp * 72.27 / 65536) ; {|* 5| in PKtoPX}
if hppp <> vppp then warning('PK font pixels non-square');
@:Warning: PK font pixels non-square}{\quad\.{PK font pixels non-square}@>
dsize:=(dsize/two_to_the_20th)*(magnification/resolution);
for pk_i:=0 to 255 do ch_loc[pk_i]:=0;
@ Of course, we need to define the above variables.
@<Glob...@>=
@!checksum : integer ; {checksum of pixel file}
@!hppp, @!vppp : integer ; {horizontal and vertical points per inch}
@!pk_loc : integer ; { byte currently being accessed in |pk_file| }
@!dummy : integer ; { used to ``absorb'' unwanted bytes from |pk_file| }
@* Character unpacking.
This is the procedure where we read the character packet and decode it into
standard pixel raster format; if a glyph is encountered that is not used in the
present document, its packet is skipped. We create one row at a time, checking
for repeat
commands, and then repeat the row as many times as is necessary. The information
gleaned from the beginning of the character packet is to be placed in various
local variables. We also check that the length of the character packet is
correct.
If the character's raster is null (as is the case with the invisible fonts used
by Sli\TeX), the |num_cols| and |num_rows| are set to suitable dummy values (see
the section $\langle$Complete remainder of Character Definition
parameters$\rangle$ for further information).
@<Unpack and write character@>=
dyn_f := flag_byte div 16 ;
flag_byte := flag_byte mod 16 ;
turn_on := flag_byte >= 8 ;
if turn_on then flag_byte := flag_byte - 8 ;
if flag_byte = 7 then
@<Read long character preamble@>
else if flag_byte > 3 then
@<Read extended short character preamble@>
else
@<Read short character preamble@> ;@/
byte_width:=(num_cols+7) div 8; {Bytes required per row in |lnf_file|}
ch_loc[char_num]:=end_of_packet-packet_length;
with glyph_map(TeX_font)(char_num) do
if loaded=wanted then
begin
base:=ras_beg+ras_len;@/
@<Process |tfm_width| information@>;
@<Validate size of character's glyph@>;
@<Complete remainder of Character Definition parameters@>;
@<Read and translate raster description@>;
@<Calculate the number of bytes of rasters transferred@>;
@<Complete the entry in the font character directory@>
end
else
@<Skip through unwanted raster@> ;
if end_of_packet <> pk_loc then abort('Bad pk file! Bad packet length.')
@.Bad pk file@>
@ A simplified version of this code is also used when printing the bitmap of
an oversized glyph in sixels.
@<Unpack and store character@>=
dyn_f := flag_byte div 16 ;
flag_byte := flag_byte mod 16 ;
turn_on := flag_byte >= 8 ;
if turn_on then flag_byte := flag_byte - 8 ;
if flag_byte = 7 then
@<Read long character preamble@>
else if flag_byte > 3 then
@<Read extended short character preamble@>
else
@<Read short character preamble@> ;@/
byte_width:=(num_cols+7) div 8; {Bytes required per row in |lnf_file|}
ch_loc[char_num]:=end_of_packet-packet_length;
if char_num=ch then
begin
base:=ras_beg+ras_len;@/
@<Read and translate raster description@>;
end
else
@<Skip through unwanted raster@> ;
if end_of_packet <> pk_loc then abort('Bad pk file! Bad packet length.')
@.Bad pk file@>
@ We need a whole lot of globals used but not defined up there. Some of these
have the same names as certain fields of |pxl_dir_type|, or of local variables
of procedures |add_txf_to_lnf| and |copy_char|. This is in order that the
segments of \.{WEB} code invoked above reference these globals therein, but the
appropriate local variables when within those procedures (when being used for
\.{PXL} files).
@<Glob...@>=
@!pk_i, @!pk_j : integer ; {index pointers}
@!flag_byte : integer ; {the byte that introduces the character definition}
@!end_of_packet : integer ; {where we expect the end of the packet to be}@/
{Following are also names of fields in a record of type |pxl_dir_type|}@/
@!num_cols, @!num_rows : sixteen_bits; {width and height of character}
@!x_off, @!y_off : signed_word ; {$x$ and $y$ offsets of character}
@!tfm_width : integer ; {character tfm width}
@#
@!tfms : array [0..255] of integer ; {character tfm widths}
@!dx, @!dy : integer ; {escapement values}
@!dxs, @!dys : array [0..255] of integer ; {escapement values}
@!ch_loc : array[0..255] of integer; {has the character been seen (and where)?}
@!dyn_f : integer ; {dynamic packing variable}
@!char_num : integer ; {the character we are reading}
@!packet_length : integer ; {the length of the character packet}
@!base : integer ; {index into |lnf_bytes|}
@!to_where : integer ; {\&{cf.} |to_where| in |copy_char|}
@!len : integer ; {\&{cf.} |len| in |copy_char|}
@!byte_width : integer ; {Bytes per row in the |lnf_file| bitmap}
@ To skip over the raster of an unwanted character, we can simply jump forward
in the PK file to |end_of_packet|.
@<Skip through unwanted raster@>=
while pk_loc<end_of_packet do dummy := pk_byte;
@ Now, the preamble reading modules. First, we have the general case: the
long character preamble format.
@<Read long character preamble@>=
begin
packet_length := get_32 ; char_num := get_32 ;
end_of_packet := packet_length + pk_loc ;
packet_length := packet_length + 9 ;
tfm_width := get_32 ;
dx := get_32 ; dy := get_32 ;
num_cols := get_32 ;
num_rows := get_32 ;
x_off := get_32 ; y_off := get_32 ;
end
@ This module reads the character preamble with double byte parameters.
@<Read extended short character preamble@>=
begin
packet_length := (flag_byte - 4) * 65536 + get_16 ;
char_num := pk_byte ;
end_of_packet := packet_length + pk_loc ;
packet_length := packet_length + 4 ;
pk_i := pk_byte ;
tfm_width := pk_i * 65536 + get_16 ;
dx := get_16 * 65536 ;
dy := 0 ;
num_cols := get_16 ;
num_rows := get_16 ;
x_off := get_16_signed ; y_off := get_16_signed ;
end
@ Here we read the most common character preamble, that with single byte
parameters.
@<Read short character preamble@>=
begin
packet_length := flag_byte * 256 + pk_byte ;
char_num := pk_byte ;
end_of_packet := packet_length + pk_loc ;
packet_length := packet_length + 3 ;
pk_i := pk_byte ;
tfm_width := pk_i * 65536 + get_16 ;
dx := pk_byte * 65536 ;
dy := 0 ;
num_cols := pk_byte ;
num_rows := pk_byte ;
x_off := pk_byte ; y_off := pk_byte ;
if x_off > 127 then x_off := x_off - 256 ;
if y_off > 127 then y_off := y_off - 256 ;
end
@ Now we have the most important part of unravelling packed pixel files, where
we actually interpret the commands in the raster description. First of all,
we need a procedure to get a single nybble from the file, as well as one to
get a single bit.
We also are now able to ``declare'' |pk_packed_num|.
@p function get_nyb : integer ;
var temp : eight_bits ;
begin
if bit_weight = 0 then begin
input_byte := pk_byte ;
bit_weight := 16 ;
end ;
temp := input_byte div bit_weight ;
input_byte := input_byte - temp * bit_weight ;
bit_weight := bit_weight div 16 ;
get_nyb := temp ;
end ;
@#
function get_bit : boolean ;
var temp : boolean ;
begin
bit_weight := bit_weight div 2 ;
if bit_weight = 0 then begin
input_byte := pk_byte ;
bit_weight := 128 ;
end ;
temp := input_byte >= bit_weight ;
if temp then
input_byte := input_byte - bit_weight ;
get_bit := temp ;
end ;
@#
@t\4@>@<Packed number procedure@>
@ Now, the globals to help communication between these procedures.
@<Glob...@>=
@!input_byte : eight_bits ; {the byte we are currently decimating}
@!bit_weight : eight_bits ; {weight of the current bit}
@!nybble : eight_bits ; {the current nybble}
@!row_addr : integer ; {whereabouts in |lnf_file| of start of each pixel row}
@!bitmap_start : integer ; {and of start of \&{first} pixel row}
@ And the main procedure.
@<Read and translate raster description@>=
bit_weight := 0 ; row_addr:=base; {Note where row of pixels starts}
bitmap_start:=base;
if dyn_f = 14 then
@<Get raster by bits@>
else @<Create normally packed raster@>
@ If |dyn_f=14|, then we need to get the raster representation
one bit at a time.
@<Get raster by bits@>=
begin
for pk_i := 1 to num_rows do begin
bite:=0; bite_weight:=7;
for pk_j := 1 to num_cols do begin
if get_bit then bite:=bite+power[bite_weight];
decr(bite_weight);
if bite_weight<0 then begin
bite_weight:=7;
put_lnf(bite);
bite:=0
end
end;
if bite_weight<7 then put_lnf(bite)
end;
end
@ We need the |power| array, which contains powers of two, as well as the |bite|
variable (which accumulates eight pixels) and |bite_weight|, which directs which
pixel is to be ``blackened''. The array |gpower| contains 0..8 black pixels,
staring at the left-hand end.
@<Globals...@>=
@!bite : integer; {accumulates 8 pixels to go to |lnf_file|}
@!bite_weight : integer; {next bit of |bite| to be filled (leftmost=0)}
@!power:array [0..7] of integer; {|power[i]| contains $2^{7-i}$}
@!gpower:array [0..8] of integer; {|gpower[i]| contains ones in leftmost |i| bits}
@ @<Set initial...@>=
power[0]:=128;
for i:=1 to 7 do power[i]:=power[i-1] div 2;
gpower[0]:=0;
for i:=1 to 8 do gpower[i]:=gpower[i-1]+power[i-1];
@ Otherwise, we translate the bit counts into the raster rows. |run_ct|
contains the number of bits of the current color, and |turn_on| indicates
whether or not they should be black. |rows_left| contains the number of
rows to be sent.
@<Create normally packed raster@>=
begin
rows_left := num_rows ;
h_bit := num_cols ;
repeat_count := 0 ;
bite:=0; bite_weight:=8;
while rows_left > 0 do begin
run_ct := pk_packed_num ;
while run_ct>0 do begin
if (run_ct<bite_weight) and (run_ct<h_bit) then begin
if turn_on then
bite:=bite+gpower[bite_weight]-gpower[bite_weight-run_ct];
h_bit:=h_bit-run_ct;
bite_weight:=bite_weight-run_ct;
run_ct:=0
end
else if (run_ct>=h_bit) and (h_bit<=bite_weight) then begin
if turn_on then
bite:=bite+gpower[bite_weight]-gpower[bite_weight-h_bit];
put_lnf(bite); bite:=0;
for pk_i:=1 to repeat_count do
for pk_j:=1 to byte_width do repeat_row;
row_addr:=base;
rows_left := rows_left - repeat_count - 1 ;
repeat_count := 0 ;
bite:=0; bite_weight:=8;
run_ct := run_ct - h_bit ;
h_bit := num_cols ;
end
else begin
if turn_on then bite:=bite+gpower[bite_weight];
put_lnf(bite);
run_ct:=run_ct-bite_weight;
h_bit:=h_bit-bite_weight;
bite:=0; bite_weight:=8
end
end;
turn_on := not turn_on ;
end ;
if (rows_left <> 0) or (h_bit <> num_cols) then
abort('Bad pk file---more bits than required!');
@.Bad pk file@>
end
@ We need to declare the repeat flag, bit counter, and color flag here.
@<Glob...@>=
@!repeat_count : integer ; {how many times to repeat the next row?}
@!rows_left : integer ; {how many rows left?}
@!turn_on : boolean ; {are we black here?}
@!h_bit : integer ; {what is our horizontal position?}
@!run_ct : integer ; {how many bits of current color left?}
@ Another necessary section of code skips over any \MF\ \.{special}s between
characters and before or after the postamble.
It is convenient to use the following definitions in creating readable |case|
statements; they will also be necessary for interpreting the \.{DVI} file.
@d four_cases(#)==#,#+1,#+2,#+3
@d eight_cases(#)==four_cases(#),four_cases(#+4)
@d sixteen_cases(#)==eight_cases(#),eight_cases(#+8)
@d thirty_two_cases(#)==sixteen_cases(#),sixteen_cases(#+16)
@d sixty_four_cases(#)==thirty_two_cases(#),thirty_two_cases(#+32)
@p procedure skip_specials ;
var i, j, k : integer ;
begin
repeat
flag_byte := pk_byte ;
if flag_byte >= pk_xxx1 then
case flag_byte of
four_cases(pk_xxx1):
begin
i := 0 ;
for j := pk_xxx1 to flag_byte do i := 256 * i + pk_byte ;
for j := 1 to i do dummy:=pk_byte ;
end ;
pk_yyy: dummy:=get_32 ;
pk_post: do_nothing;
pk_no_op: do_nothing;
pk_pre, eight_cases(pk_pre+1):
abort('Bad pk file; unexpected byte: ', flag_byte:1,'!') ;
@.Bad pk file@>
endcases ;
until (flag_byte < pk_xxx1) or (flag_byte = pk_post) ;
end ;
@* Pixel file format.
A \.{PXL} file is an expanded raster description of a single font at a
particular resolution and contains essentially the same information as
that contained in a \.{GF} file. \.{PXL} files are used by many existing
device-driver programs for dot matrix devices. By convention, \.{PXL} files
are for 200 pixels per inch. However, the LN03 requires pixel files generated
at 300 pixels per inch; the font naming conventions for access to these files
are specified elsewhere.
All words in a \.{PXL} files are in 32-bit format, with the four lower
bits zero on 36-bit machines. The raster information is contained in a
sequence of binary words which record white pixels as zeros and black
pixels as ones.
The first word of the \.{PXL} file and the last word contain the |pxl_id|
which is currently equal to 1001.
This first word is followed by a sequence of raster information words where
each line of pixels in the glyphs is represented by one or more words of
binary information. The number of words used to represent each row of pixels
for any particular glyph is fixed and it is set by the value of
|max_m-min_m+1| for that particular glyph. Each white pixel is represented by
a zero and each black pixel is represented by a one in the corresponding bit
positions (the first 32 only of each word on 36-bit machines). The unused bit
positions toward the end of each set of words for each row of pixels are
filled with zeros. It should be noted that this representation is more
wasteful of space than it needs to be, but it may possibly simplify the
subsequent use of the information by a device-driver program.
The font directory follows, occupying a fixed position with respect to the
end of the file (in words 517 through 6 from this end), and assigns 4
words for each of the potential 128 different glyphs that could be
contained in this particular font in the order of their ascending ascii
values (not in the order that the glyphs appear in the raster section,
which may be entirely arbitrary). This means that the first four words are
for the ascii zero glyph. All four words reserved for any missing glyphs
are set to zero. A detailed description of these directory entries is given in
the next section.
The final five words in the \.{PXL} file contain information relative to
the entire file.
The first of these five words is a checksum which should
match the checksum contained in the \.{TFM} file that \TeX\ used in
reference to this font, although, if this checksum is zero, no validity
checking will be done.
The second of these five words is an integer that is 1000 times the
magnification factor at which this font was produced.
The third word contains the design size of the font measured in \.{FIXes}
($2^{-20}$ unmagnified points).
The fourth word contains a pointer to the first word of the font directory.
The fifth (and last word of the entire file) contains a duplicate of the
|pxl_id| as contained in the first word of the file.
@d pxl_id=1001 {current version of \.{PXL} format}
@ As mentioned above, the pixel file's glyph directory consists of four
longwords for each possible glyph. The use of each of these longwords is as
follows:
\yskip\hang\&{Word 0.} This is treated as being split into two unsigned
sixteen-bit quantities, containing the Pixel Width in the left half-word (the
leftmost 16 bits) and the Pixel Height in the right half-word (the next 16
bits). These dimensions are those of the smallest bounding-box, measured in
pixels, and they have nothing necessarily to do with the width and height
figures that appear in the \.{TFM} file.
\yskip\hang\&{Word 1.} Again is split into two sixteen-bit half-words, but
this time they are each treated as \\{signed} quantities. Together, these give
the offset of the glyph's reference point \&{from} its upper-left-hand corner
of the bounding box, measured in pixels, with the $x$-offset ($\delta x$) in the left
half-word and the $y$-offset ($\delta y$) in the right half-word. Two's complement
representation is used. Remember that although the positive $x$ direction means
`rightward' and positive $y$ is `downward' on the page, these offsets specify
the distance \&{to} the reference point \&{from} the upper left-hand corner of
the raster with +ve $x$ implying that the left-most column of the raster is to
the \&{left} of the reference point, and +ve $y$ implying that the top-most
row of the raster is \&{above} the baseline.
\yskip\hang\&{Word 2.} This contains the number of the word in this \.{PXL}
file where the Raster Description for this particular glyph begins, measured
from the first word of the file, which is numbered zero.
\yskip\hang\&{Word 3.} Contains the \.{TFM} width, measured in \.{FIXes},
where 1 \.{FIX} is $1/(2^{20})$ times the design size.
We now define an appropriate structure to hold such directory entries:
@<Types in the outer block@>=
@!signed_word= [word] -32768..32767;@/
@!pxl_dir_type=packed record@/
num_cols,num_rows : sixteen_bits;
x_off,y_off : signed_word;
addr : [byte(4)] integer;
tfm_width : [byte(4)] integer
end;
@ We need a |file| object to reference the pixel file, and also an array to hold
its directory. Furthermore, when a pixel file directory is read in, we need
variables |magnification| and |dsize| to hold quantities taken from the last
five longwords of the file.
@<Globals in the outer block@>=
@!pxl_file: byte_file;
@!pxl_dir : packed array [0..127] of pxl_dir_type;
@!pxl_block,@!pxl_ptr : integer;
@!pxl_size:integer;
@!magnification,@!dsize:real;
@*Procedures to access the pixel file. The following definitions and
declarations provide a means of accessing bytes, words (signed \AM\ unsigned)
and longwords in the |pxl_file|.
@d pxl_byte(#)==pxl_file^[#]
@d pxl_word(#)==(pxl_byte(#+1)+256*pxl_byte(#))
@d pxl_long(#)==(pxl_byte(#+3)+256*(pxl_byte(#+2)+@|256*(pxl_byte(#+1)+@|
256*pxl_byte(#))))
@d must_get(#)==
if # = VAX_block_length then
begin get(pxl_file,VAX_continue);
incr(pxl_block); #:=0
end
@p procedure move_to_pxl(n:integer);
begin
if n div VAX_block_length <> pxl_block then
begin pxl_block:=n div VAX_block_length;
VAX_find_block(pxl_file,pxl_block+1,VAX_continue)
end;
pxl_ptr:=n mod VAX_block_length
end;
@ The |long_pxl| function reads a longword (in Bigendian order) from the
|pxl_file| and converts it to correct (signed) VAX longword notation.
@p function long_pxl(var index:integer) : integer;
begin
if pxl_byte(index)<=@"80 then
long_pxl:=pxl_long(index)
else
long_pxl:=pxl_byte(index+3)+256*(pxl_byte(index+2)+256*(
pxl_byte(index+1)+256*(pxl_byte(index)-256)));
index:=index+4;
must_get(index)
end;
@ The |sign_pxl_word| function reads a signed shortword (two bytes).
@p function sign_pxl_word(var index:integer) : integer;
begin
if pxl_byte(index)>=@"80 then
sign_pxl_word:=pxl_word(index)-@"10000
else
sign_pxl_word:=pxl_word(index);
index:=index+2;
must_get(index)
end;
@ The |word_pxl| function reads an unsigned two-byte word.
@p function word_pxl(var index:integer) : integer;
begin word_pxl:=pxl_word(index);
index:=index+2; must_get(index)
end;
@*Handle rasters of oversized glyphs. Because of a physical limitation of the
LN03, it is not legal to download the glyph of a character which would occupy
more than 5,700 bytes. (Details appear under ``\&{Copy one character's
rasters}''.)
Any such glyph which has not been downloaded will have its |loaded| flag set to
|no| in its |glyph_map| entry. When we attempt to add such a character to
the output lists, we add instead a separate segment which indicates that it
holds a single oversized glyph. When this segment is eventually processed,
we output the glyph itself as a bitmap dump, using sixel graphics.
To save us having to keep on opening and closing the pixel file containing
the glyph, we maintain the ``name'' of the currently open file in a global
string, and only open a new file if the glyph required comes from a
different \TeX\ font. The global variable |open_file_name| is a textual
representation of the full file specification of the currently open pixel
file. (Note that this same variable name also appears as a \&{formal
parameter} of the |add_txf_to_lnf| function; it is referenced in the
$\langle$Open the file containing the glyph bitmaps$\rangle$ W{\mc EB}
section, used in the following code, and the aforementioned function.)
@<Globals...@>=
@!open_file_name : file_spec;
@ Of course, when we start (and even after we've created the font file(s) for
down-loading) there is no pixel file open. Let's indicate that:
@<Set initial...@>=
open_file_name:='';
@ Procedure |print_glyph| has to refer to |change_pixel_file|, which is more
convenient to declare along with the font downloading procedures. So we'll
cheat and put a |forward| declaration here!
@p procedure change_pixel_file( TeX_font : integer); forward;
@ The procedure |print_glyph| opens the associated pixel file (if necessary)
by calling |change_pixel_file|. It firstly outputs absolute positioning
commands to establish the correct ``printing'' position (the strange
|bitmap_offset| was incorporated when the reference to the glyph was stored
in the |segment|), and then outputs suitable escape sequences to locate the
printing position at the correct offset relative to that position, as
determined by the glyph's first pixel relative to its reference point.
If the pixels come from a |pixel_file|, then we can locate the required
character glyph through the character directory. In the case of a
|packed_file|, it may happen than this character has already been output, and
so its position within the file is known; in this case either it was the last
character read (has already been dumped as a bitmap), when its glyph will be in
the |lnf_file|: otherwise we position |pk_loc| to the correct position in the
file. If the character has never been read (|ch_loc=0|) then we read and
process characters into the \&{same} part of the |lnf_file| until the desired
character has been read. When reading |packed_file|s, we utilize an entry in
the |pxl_dir| in preference to the globals |x_off|, etc, so save repetition of
many lines of \.{WEB} code.
The lines of pixel rasters are then output, six at a time, in sixel graphics
format.
@p procedure print_glyph(@!glyph_seg : segment; @!y_loc : integer);
var @!sixel_line : array [1..6,1..300] of eight_bits;
@!i,@!j,@!k,@!l,@!m,@!n,@!len : integer;
@!last_sixel,@!matching: integer;
@!ch : integer;
@<Declare the procedure |put_sixel|@> ;
begin
write_ln(ln3_file);
change_pixel_file(glyph_seg.font);@/
write(ln3_file,csi,y_loc:1,'d',csi,glyph_seg.xs:1,'`');
ch:=glyph_seg.ch;
with pxl_dir[ch] do
begin
@<Read raster for oversized glyph into memory@> ;
@<Prepare to write glyph bitmap at correct position@> ;
write_ln(ln3_file,dcs,'9;0;1q'); {Prepare to meet a sixel dump!}
@<Convert glyph's raster to sixels@>;
write_ln(ln3_file,st); {Finish sixel dump}
end; {with}
end;
@ We are making use of a record of |pxl_dir| to hold the various quantities
regarding the dimensions of the glyph which have been (or are about to be) read
from the font file. In the case of an unpacked raster, we need only position
the file to the correct longword, but for a packed file, it's now time to
start reading the bytes and reconstituting the raster in a free part of the
|lnf_file|.
@<Read raster for oversized glyph into memory@>=
if pxl_ident=pixel_file then move_to_pxl(addr*4)
else begin
ras_beg:=2*VAX_block_length;
if ch_loc[ch]=0 then
@<Find packed raster and read into memory@> ;
if char_num<>ch then
@<Move back in packed file and read raster into memory@> ;
base:=ras_beg
end;
@ If the character has not yet been read from the packed font file, we need to
read forwards until the character is met. We then are able to access its
raster, which will have been created in memory as the character was read.
The character cannot be missing or the font download would not have resulted
in the |loaded| field having the value |no|.
@<Find packed raster and read into memory@>=
repeat ras_len:=0;@/
@<Unpack and store character@>;
skip_specials
until (ch_loc[ch]<>0) or (flag_byte=pk_post);
@ If we've previously read the wanted character's glyph, but it has since been
superseded by another's, we need to backtrack the packed font file to the
correct position and read the raster all over again.
@<Move back in packed file and read raster into memory@>=
begin {character not already in |lnf_file|}
pk_loc:=ch_loc[ch];
VAX_find_block(pxl_file,(pk_loc div VAX_block_length)+1,VAX_continue);
ras_len:=0; flag_byte:=pk_byte; {We know there's no \.{special}s}@/
@<Unpack and store character@>;
skip_specials
end
@ Having read the glyph into memory, we now need to position the LN03 to start
writing its first sixel at the appropriate position relative to the reference
point. We do this by sending Horizontal and Vertical Positioning control
sequences, either Relative for forward movement, or Backward when negative
movement is necessary. The signs are reversed because the offsets are those
of the top left-most pixel of the glyph relative to the reference point.
@<Prepare to write glyph bitmap at correct position@>=
if x_off<0 then
write(ln3_file,csi,abs(x_off):1,'a') {Horizontal Position Relative}
else
if x_off>0 then write(ln3_file,csi,x_off:1,'j');{Horizontal Position Backward}
if y_off<0 then
write(ln3_file,csi,abs(y_off):1,'e') {Vertical Position Relative}
else
if y_off>0 then write(ln3_file,csi,y_off:1,'k') {Vertical Position Backward}
@ This routine outputs the sixel character, |x|, in the optimum sixel encoding
making use of repeat counts if the same sixel is repeated more than twice.
The current output record is terminated if it's getting too long, and a new
one commenced.
@<Declare the procedure |put_sixel|@>=
procedure @!put_sixel(x:integer);
begin
if x = last_sixel then incr(matching) else
@<Output |matching| copies of |last_sixel|@> ;
if len >=128 then
begin
write_ln(ln3_file);
len:=0
end
end
@ If we have a new sixel (|x|) which differs from the previous one
(|last_sixel|) we have to output the latter the correct number of times. The
number of successive calls of |put_sixel| will have been counted in
|matching|. After performing the output, we record that |x| has now been
``output'' once.
@<Output |matching| copies of |last_sixel|@>=
begin
if matching < 3 then
@<Output explicit copies of |last_sixel|@>
else
@<Output repeat count for |last_sixel|@> ;
matching:=1;
last_sixel:=x
end
@ If |last_sixel| has only been output once or twice, we send the explicit
copies of it
@<Output explicit copies of |last_sixel|@>=
begin
len:=len+matching;
while matching > 0 do
begin
write(ln3_file,chr(last_sixel));
decr(matching)
end
end
@ On the other hand, it's more efficient to use a sixel repeat count if there
are three or more copies of |last_sixel| required. The introducer for such a
repeat count is the `\.!' character, which is followed by the ASCII character
representation of the count. The actual sixel to be output then follows this
count.
@<Output repeat count for |last_sixel|@>=
begin
write(ln3_file,'!',matching:1,chr(last_sixel));
len:=len+5
end
@ To produce the bitmap sixel dump of the glyph, we read the pixels into the
array |sixel_line|; this contains 6 rows, each capable of holding 300
|eight_bits|, containing 8 pixels of the character's raster. We index this with
|k|, which thus runs |1..6|
Variable |i| is used to run through the |num_rows| lines of rasters, whilst |j|
indexes the bytes from the pixel file, and into |sixel_line|.
@<Convert glyph's raster to sixels@>=
i:=0; k:=1;
while i < num_rows do
begin
@<Read rows of pixels into sixel buffer@>;
incr(k); incr(i);
if k > 6 then
begin
@<Output line of sixels@>;
k:=1
end
end;
if k>1 then
begin
while k <= 6 do
begin {Fill in bottom incomplete rows of sixels}
for j:=1 to (num_cols+7) div 8 do
sixel_line[k,j]:=0;
incr(k)
end;
@<Output line of sixels@>
end
@ The following section outputs the six rows of pixels in
|sixel_line| as $8\times{|num_cols|}$ columns of sixel graphics.
The variable |m| runs through the values 128,64,32,..,1, and is used to divide
an |eight_bit| to extract the most-significant bit of eight pixels in rows 6..1
(indexed by |n|) of |sixel_line|. Each bit is transferred into the least
significant bit of |l|, initially zeroed for each sixel.
As each sixel is completed, it is output to the \.{LN3} file. To prevent the
output of too many characters to the |ln3_file|, which is of type |text|, the
output is broken every 128 bytes. A ``graphics'' end-of-record is inserted
after every line of sixels.
@<Output line of sixels@>=
len:=0; last_sixel:=0; matching:=0;
for j:=1 to (num_cols+7) div 8 do
begin
m:=128;
while m>0 do
begin
l:=0;
for n:=6 downto 1 do
if odd(sixel_line[n,j] div m) then
l:=2*l + 1
else
l:=2*l;
put_sixel(l+63);
m:=m div 2
end;
end;
put_sixel(0); {Flushes the |last_sixel| from the ``buffer''}
write_ln(ln3_file,'-') {Sixel `CR-LF'}
@ We are now in a position to be able to include the declaration of |ord_text|
into the \PASCAL\ generated from the \.{WEB}
@p @<Declare the |ord_text| Procedure@>;
@*Output the individual segments forming a page. At the end of each page, we
have a number of linked lists containing each of the segments set on any
individual ``line'' of the paper. We have to re-order these, such that they
are output in the most efficient manner, with appropriate changes of (lnf)
font as necessary.
When pages are actually being ``set'', |font_in_use[lnf]| will contain either
-1, which indicates that the LN03 font required is not currently mapped to a
|SGR| (Select Graphic Rendition) code, or a number in the range |0..9|, from
which the |SGR| font number to be used to invoke that font as the current one is
derived (by adding 10).
The array |who_uses| maps these |SGR| indices back to the number of an LN03
font. Finally, the current |SGR| in use is held in |cur_out|, to minimise
the number of |SGR| commands that need to be included in the output stream.
@<Globals...@>=
@!font_in_use:array [0..max_lnfonts] of -1..max_SGR;
@!who_uses:array [0..max_SGR] of -1..max_lnfonts;
@!cur_out:0..max_SGR;
@ First of all, we shall require a procedure to output an arbitrary string
(most commonly an escape sequence) to the LN03 printer. This will typically
be used to activate the |HPA| (Horizontal Position Absolute) control sequence.
@p procedure esc_out(str:varying[str_len] of char); {Escape sequence to LN3}
begin
ln3_cnt:=ln3_cnt+VAX_length(str);
write(ln3_file,str)
end;
@ Then we require a procedure to output as much of a segment as
possible to the current line, and delete that section from the segment as
stored before returning.
One useful extension of VAX-\PASCAL\ that we utilize are the functions |min|
and |max|. These each take an arbitrary number of parameters, and return a
result, of the same type, that is equal to the smallest or largest
(numerically) of the parameters provided.
@d VAX_min==@= min@>
@d VAX_max==@= max@>
@p procedure print_seg;
var @!text_len,@!uselen,@!i,@!j,@!ln_font,@!delta_x:integer;
@!csi_present:integer;
begin
with cur_seg^ do
begin
text_len:=VAX_length(text);
uselen:=VAX_min(text_len,max_line-ln3_cnt,max_print-ln3_print);
if (uselen>0) or (text_len=0) then {`Use' empty segment}
begin {Otherwise, throw segment back if none of it will fit!}
if uselen <> text_len then {Text won't all fit!}
@<Determine position at which to split output line@>;
@<Select correct font, if necessary@> ;
@<Set horizontal position on paper@>;
write(ln3_file,substr(text,1,uselen));
@<Count characters output to printer, perform split if necessary@>
end
end
end;
@ If a segment is too long to fit in its entirety upon the output line to be
sent to the LN03, we split it into smaller segments. This cannot be done at
an arbitrary cut-off point, since the segment might include embedded control
sequences (which will be |HPR| ones, ending with `\.a'); therefore, we need to
select a split point which will not destroy the contiguity of such sequences.
@<Determine position at which to split output line@>=
begin
csi_present:=0;
@<Find rightmost \.{ESC} or \.{CSI} character in segment@> ;
@<Split \\{after} the |HPR| control sequence, if in last 8 characters@>
end
@ Finding the right-most control sequence starts off by locating the character
which introduces it; this used to be \.{ESC} but nowadays the eight-bit
\.{CSI} character is used instead.
@<Find rightmost \.{ESC} or \.{CSI} character in segment@>=
for i:=1 to uselen do
if (text[i] = csi) or (text[i] = esc) then csi_present:=i
@ If that right-most control sequence introducer was earlier than eight
characters from the end of the part of the string that we \\{can} use (or
didn't exist) there's no need to split the segment. Otherwise, we need to
locate the `\.a' which completes the Horizontal Position Relative sequence,
and split after it.
@<Split \\{after} the |HPR| control sequence, if in last 8 characters@>=
if (csi_present>0) and ((uselen-csi_present)<=8) then
begin i:=csi_present;
while (i<uselen) and (text[i]<>'a') do incr(i);
if text[i]<>'a' then uselen:=csi_present-1
end
@ Whenever we output a segment, we must ensure that the LN03 will image the
characters therein from the correct downloaded font. The following code
firstly checks whether the font is currently designated (and thus selectable)
within the LN03. It then selects the necessary font by outputting a suitable
|SGR| (Select Graphic Rendition) to access the appropriately designated font.
@<Select correct font, if necessary@>=
if font_in_use[font]<0 then {not currently designated}
@<Select Graphic Rendition@>;
if font_in_use[font]<>cur_out then
begin cur_out:=font_in_use[font];@/
esc_out(csi+str_int(10+cur_out)+'m'); {|SGR| for the font}
end
@ Although we permit a maximum of |max_lnfonts| to have been downloaded to the
LN03, the machine itself may only access one of ten of these through the
ISO-standard |SGR| (Select Graphic Rendition) control sequence. If a
different font is required, it may be necessary to designate that it shall be
selectable by a different |SGR| sequence.
@<Select Graphic Rendition@>=
begin
font_in_use[who_uses[max_SGR]]:=-1;
font_in_use[font]:=max_SGR;
who_uses[max_SGR]:=font;
esc_out(dcs+'1;'+str_int(10+max_SGR)+'}U0000'+@=dec@>(font,2,2)+
'002SK00GG'+st)
end
@ We need to output appropriate ISO-standard control sequences to position the
segment at the correct position on the current line; if the segment is the
first to be output on the current line, this positioning should use the |HPA|
(Horizontal Position Absolute) sequence, otherwise the |HPR| (Horizontal
Position Relative) sequence is preferred.
@<Set horizontal position on paper@>=
if cur_seg=out_list then {Leftmost segment of line}
esc_out(csi+str_int(xs)+'`') { HPA to |xs|}
else@/
begin
delta_x:=xs-prev_seg^.xe; {Distance from end of next leftward segment}
if delta_x>0 then esc_out(csi+str_int(delta_x)+'a') {HPR}
else if delta_x<0 then abort('Internal error (-ve delta x)')
@:fatal error Internal error ...}{\quad\.{Internal error}@>
@:fatal error Internal error negative delta x}{\qquad\.{(-ve delta x)}@>
end
@ After we have output the first |uselen| characters to the \.{LN3} file, we
then check whether that's finished off this segment. If it has, we can
discard the segment, but otherwise we perform the split at the predetermined
point.
@<Count characters output to printer, perform split if necessary@>=
ln3_cnt:=ln3_cnt+uselen; ln3_print:=ln3_print+uselen;
if uselen=text_len then
cur_seg:=nil {All of segment used}
else
@<Split segment which would create too long an output line@>
@ Having previously determined the split point for the segment (including none
at all), we now reduce the segment to the part following that point.
This is complicated by the fact that we must determine whereabouts we have
actually got to on the current line. To do this, we increment the ``current
position'' by each character's width, whilst any |HPR| sequences have to be
``read'' in as numbers and added to the count.
@<Split segment which would create too long an output line@>=
begin i:=1;
while i<=uselen do
begin
if (text[i]<>esc) and (text[i]<>csi) then
xs:=xs+ch_widths[font,ord(text[i])]
else
@<Read back the horizontal skip specified in the |HPR| sequence@> ;
incr(i);
end;
text:=substr(text,uselen+1,text_len-uselen)
end
@ Whenever we meet a \.{ESC} or \.{CSI} character, we assume that it starts a
Horizontal Position Relative command, locate the first digit of its parameter
(which will follow a `\.[' after the \.{ESC} or come immediately after the
\.{CSI}), and the `\.a' which finishes the control sequence. The characters in
between represent a decimal number, which we can ``read'' using a Vax-\PASCAL\
extension. The current placing position is then incremented by this amount.
@<Read back the horizontal skip specified in the |HPR| sequence@>=
begin
if text[i]=esc then j:=i+2 else j:=i+1;
while text[j]<>'a' do incr(j);
if text[i]=esc then
VAX_string_read(substr(text,i+2,j-i-2)+' ',delta_x)
else
VAX_string_read(substr(text,i+1,j-i-1)+' ',delta_x);
xs:=xs+delta_x; i:=j;
end
@ As we shall see later, if the string of characters in a \TeX\
\.{\\special} command commences with the character string |'SX '| (i.e.,
`old style'), or |'ln03:plotfi[le] '| or |'ln03:sixel '| (`new style'), a
segment will be inserted into the page list referencing the appropriate
file; when the time comes to process that segment, the following procedure
will be invoked. The |text| field of |cur_seg| will be the name of a
VAX/VMS file which is assumed to contain an image encoded into DEC's
standard ``sixel'' format.
This file is then opened, and the records therein copied to the \.{.LN3}
file; the |y_position| parameter, and the starting position recorded in
field |xs| are used in appropriate escape sequences to position this
graphics dump at the desired position on the page before this operation.
The name of the file included is reported on the terminal and in the log file.
@p procedure Copy_sixel_file(@!y_position:integer);
var
@!open_stat : integer; {records whether |open| was successful}
@!sixel_record, {holds records being copied from included file}
@!stat_msg : varying [256] of char; {just holds an error indication}
@!sixel_file : text; {The file of graphics details to be copied}
begin
open(sixel_file,cur_seg^.text,@=readonly@>,@=256@>,
@=user_action@>:=file_open,VAX_continue);
open_stat := status(sixel_file);
if open_stat<>0 then
begin
case open_stat of
2: stat_msg := 'PAS-E-ERRDUROPE';
3: stat_msg := 'PAS-E-FILNOTFOU';
5: stat_msg := 'PAS-E-ACCMETINC';
6: stat_msg := 'PAS-E-RECLENINC';
7: stat_msg := 'PAS-E-RECTYPINC';
8: stat_msg := 'PAS-E-ORDSPEINC';
othercases
stat_msg := str_int(open_stat);
endcases;
error('Couldn''t open \special inclusion: ',cur_seg^.text,
' - ignored (status=',stat_msg,')');
@:Error: Couldn't open special inclusion}{\quad\.{Couldn't open \\special inclusion}@>
end else
begin
monitor(' ('+def_file_name);
print_ln('Sixel graphics included from: ',def_file_name);
@.Sixel graphics included...@>
reset(sixel_file);
write(ln3_file,csi,y_position:1,'d',csi,cur_seg^.xs:1,'`');
while not eof(sixel_file) do
begin
read_ln(sixel_file,sixel_record);
write_ln(ln3_file,sixel_record)
end;
close(sixel_file,@=disposition:=save@>,VAX_continue);
monitor(') ');
write_ln(ln3_file,SSU_pixel); {Make sure that we still have pixel SSUs}
end;
end;
@ Whilst traversing the lists, we require some additional variables:
During this output phase, we split the lines
of output such that no line contains more than |max_line| characters
altogether, of which no more than |max_print| may be printable ones. The
counters |ln3_cnt| and |ln3_print| permit us to exercise this control. The
|boolean| variable |top_of_page| controls the inclusion of a |FF| character in
the output file to split the pages.
If the page contains glyphs in both of the possible orientations, this is
recorded in |two_orientations|. Segments corresponding to the orientation
not currently being output are shuffled off onto the |deferred_list| whilst
this processing takes place.
@<Globals in the ...@>=
@!out_list,@!prev_seg,@!right_seg,@!left_seg:^segment;
@!x_left,@!x_max:integer;
@!ln3_cnt,@!ln3_print:integer;
@!still_fits,top_of_page:boolean;
@!two_orientations:boolean;
@!deferred_list:^segment;
@ The actual traversing of the lists takes place here; since we now support
the concept of mixing orientations on the one page, it's sometimes necessary
to traverse the lists twice!
@<Output contents of page@>=
repeat
two_orientations := false; {assume that we \&{don't} need two passes!}
for k:=y_top to y_bot do {Cycle through each possible list}
if seg_list[k] <> nil then
begin
out_list:=nil; deferred_list:=nil;
repeat {until we've emptied current line}
@<Save segments which require opposite orientation@> ;
@<Process bitmaps and other ``empty'' segments@>;
@<Construct a list of non-overlapping segments@> ;
@<Output list of non-overlapping segments@> ;
until seg_list[k]=nil;
if deferred_list<>nil then two_orientations:=true;
seg_list[k]:=deferred_list
end;
if two_orientations then {prepare for second (and last) pass}
begin
if cur_orient=landscape then new_orient:=portrait
else new_orient:=landscape;
@<Change page orientation@>
end;
until not two_orientations;
if lost_seg <> nil then
begin cur_seg:=lost_seg;
finish_seg {Output remaining errors}
end;
@ If, when outputting stored information in |cur_orient|, we encounter some
segment(s) in the opposite orientation, we save them in a subsidiary list,
and stuff them back into |seg_list[k]| after processing the current row.
@<Save segments which require opposite orientation@>=
prev_seg:=nil; cur_seg:=seg_list[k];
repeat
right_seg:=cur_seg^.next; {we know that |cur_seg<>nil|}
if cur_seg^.direction<>cur_orient then
begin
if prev_seg<>nil then
prev_seg^.next:=right_seg {in middle of list}
else
seg_list[k]:=right_seg; {at beginning of list}
cur_seg^.next:=deferred_list; {add segment to front of deferred list}
deferred_list:=cur_seg
end else
prev_seg:=cur_seg; {latest non-empty node}
cur_seg:=right_seg {next node on |seg_list|}
until cur_seg=nil
@ Now that the program can support landscape and portrait orientation within
the same document, we need a method of telling the LN03 to switch between
the modes. This code is called before output of the first object to the
page, if |new_orient<>orientation|, and again at the end of the page, to
reset to the default |orientation| for the next page.
@<Change page orientation@>=
if new_orient<>cur_orient then
begin
if new_orient=landscape then
begin
write_ln(ln3_file,csi,PFS_landscape);
page_len:=paper_wid+page_x_min; page_wid:=paper_ht+y_land_min;
y_min:=page_x_min; x_min:=y_land_min
end else
begin
write_ln(ln3_file,csi,PFS_portrait);
page_len:=paper_ht+y_port_min; page_wid:=paper_wid+page_x_min;
y_min:=y_port_min; x_min:=page_x_min
end;
cur_orient:=new_orient
end
@ When handling characters from a virtual font, the situation can arise in
which a totally empty segment (one that contains no text) has been added to
the list. Such segments can be discarded! Other segments can appear to
contain no text, but actually reference a single oversized glyph, which must
therefore be output as a bitmap.
@<Process bitmaps and other ``empty'' segments@>=
prev_seg:=nil; cur_seg:=seg_list[k];
if cur_seg<>nil then
repeat
right_seg:=cur_seg^.next; {we know that |cur_seg<>nil|}
if (VAX_length(cur_seg^.text)=0) or (cur_seg^.seg_type<>glyph_string) then
begin
@<Output segment which references an external bitmap@>;
if prev_seg<>nil then
prev_seg^.next:=right_seg {in middle of list}
else
seg_list[k]:=right_seg; {at beginning of list}
cur_seg^.next:=free_seg_list; {add segment to front of free list}
free_seg_list:=cur_seg
end else
prev_seg:=cur_seg; {latest non-empty node}
cur_seg:=right_seg {next node on |seg_list|}
until cur_seg=nil
@ Character glyphs which were too large to be downloaded into the LN03's
internal fonts have the |seg_type| field of the segment set to |bitmap|;
when we meet such a segment, we output the bitmap immediately. Similarly,
sixel dumps referenced in \.{\\special} commands have the value |sixel_dump|
for this field, and |Copy_sixel_file| is called to copy the referenced file.
@<Output segment which references an external bitmap@>=
case cur_seg^.seg_type of
bitmap: print_glyph(cur_seg^,k);
sixel_dump: Copy_sixel_file(k);
othercases do_nothing;
endcases
@ For as many as possible of all the segments which belong at the current
absolute vertical position, we construct a list in which none of the segments
overlap each other. We are then able to output these in one sequence which
will contain only characters and \&{forward} horizontal movement commands.
@<Construct a list of non-overlapping segments@>=
x_left:=1000000000; {A very high value; ensure |right_seg| \AM\ |left_seg| get set}
repeat
prev_seg:=nil; right_seg:=nil;
x_max:=-1;
cur_seg:=seg_list[k];
@<Find segment of current line which extends furthest to the right@> ;
@<Transfer right-most segment to the |out_list|@>
until right_seg = nil;
@ We start by locating the segment (of those available) which extends furthest
to the right, and also the one which precedes it. These are then pointed to
by |right_seg| and |prev_seg|, respectively.
@<Find segment of current line which extends furthest to the right@>=
while cur_seg<>nil do
begin
if (cur_seg^.xe > x_max) and (cur_seg^.xe <= x_left) then
begin x_max:=cur_seg^.xe;
right_seg:=cur_seg; {Points to rightmost segment}
left_seg:=prev_seg {Points to first of at least 2, or is |nil|}
end;
prev_seg:=cur_seg;
cur_seg:=cur_seg^.next;
end
@ At this point, we have |right_seg| pointing to the segment which extends
furthest to the right, whilst |left_seg| will point to the segment immediately
before this (which may, or may not, overlap |right_seg|).
We then put it at the front of the |out_list|; further non-overlapping
segments will later be placed in front of it. We ensure that the next
|right_seg| that we select will not overlap by noting the left-most extent of
the segment we've just put on the |out_list|.
@<Transfer right-most segment to the |out_list|@>=
if right_seg <> nil then
begin
@<Remove |right_seg| from list@> ;
right_seg^.next:=out_list; {Put rightmost at start of |out_list|}
out_list:=right_seg;
x_left:=right_seg^.xs {Don't consider anything right of its LHS}
end
@ If |left_seg| is |nil| then |right_seg| is the first segment associated
with the line, and so we remove it from the front of the list. Otherwise, we
detach it from the middle of the list.
@<Remove |right_seg| from list@>=
if left_seg = nil then
seg_list[k]:=seg_list[k]^.next
else
left_seg^.next:=left_seg^.next^.next
@ Once we've constructed the list out of as many segments as possible for the
current ``line'', we can output them all and go back for some more.
@<Output list of non-overlapping segments@>=
while out_list <> nil do
begin
ln3_cnt:=0; ln3_print:=0; {Nothing as yet on line}
esc_out(csi+str_int(k)+'d'); {Move to correct line on paper}
cur_seg:=out_list; prev_seg:=nil;
still_fits:=true;
@<Output current segment, in chunks if necessary@> ;
@<Return processed segments to the |free_seg_list|@>;
write_ln(ln3_file)
end
@ Once we have a single segment ready for output, we call |print_seg| to
output as much of it as will fit in the space remaining in the output buffer.
If this is the whole segment, we can then repeat the process with the next
segment in the |out_list|, noting the address of this segment so that it may
be returned to the |free_seg_list|. On the other hand, if |print_seg| decided
to split the segment, we break out of the loop.
@<Output current segment, in chunks if necessary@>=
while (cur_seg <> nil) and still_fits do
begin right_seg:=cur_seg;
print_seg; {Will leave |cur_seg=nil| if all text output}
if cur_seg = nil then
begin
cur_seg:=right_seg^.next; {Next segment for output}
prev_seg:=right_seg
end else
still_fits:=false;
end;
@ Once a segment (or group of segments) have been output, it is safe to
discard them. We do this by returning them to the |free_seg_list|, so that
new nodes will be allocated therefrom in preference to using |new|. The first
such freed segment is that at the begining of the |out_list|, whilst the last
is that most recently output, noted in |prev_seg|.
@<Return processed segments to the |free_seg_list|@>=
if prev_seg <> nil then
begin prev_seg^.next:=free_seg_list;
free_seg_list:=out_list;
out_list:=cur_seg
end
@ When \.{DVItoLN03} first starts, any output produced will be printed on the
first page. Subsequently, it is necessary to insert a form-feed character to
terminate the preceding page before any output for another page may commence.
The |boolean| |top_of_page| is used to keep track of whether the form-feed is
required.
This code is called whenever something is about to be output to the page: if
the orientation is incorrect at that time, the appropriate change is made.
It's also the appropriate time to change paper trays, and select the
printing mode.
@<Move to top of form@>=
if top_of_page then
begin
top_of_page:=false;
if pages_printed>0 then write_ln(ln3_file,FF);
@<Change tray for second sheet, if requested@>;
@<Skip to a \\{recto} page, if necessary@>
end;
@<Change page orientation@>
@ With the DEClaser~2200, it is possible (through the \.{FIRST} and \.{REST}
selectors of the \.{/FEED\_TRAY} qualifier) to arrange for the second and
subsequent sheets to be printed on paper from a different input tray than
that used for the first sheet. The code in this module effects this tray
change, when the second sheet to be printed is first met: it firstly
determines whether we're printing a \\{recto} page, when duplexing (this
information is later used for selecting the printing mode for the current
page). The ``cascade'' of |if| clauses is to reduce the processing overhead
except for the first three pages printed.
The macro here is used to change trays; the control sequence ending in
|'!v'| is the |DECASTC| (Automatic Sheet-feeder Tray Control) command, to
select the desired tray.
@d select_tray(#)==case # of
default_tray: do_nothing;
othercases
write(ln3_file,csi,#:1,'!v')
endcases
@d odd_page==(odd(count[0])or(count[0]=0))
@d recto_page==(duplex_by_page_numbers and odd_page) or
(not duplex_by_page_numbers and not odd(pages_printed))
@d starting_second_sheet==
(((print_mode<true_duplex_normal)or(print_mode>true_duplex_tumbled)) and
(pages_printed=1)) {non-duplex; change at second page}
or
(((print_mode=true_duplex_normal)or(print_mode=true_duplex_tumbled)) and
this_page_recto) {duplex; change at second sheet}
@<Change tray for second sheet, if requested@>=
this_page_recto:=recto_page;
if pages_printed<=2 then
if pages_printed>0 then
if starting_second_sheet then
if following_tray<>first_tray then
select_tray(following_tray)
@ When we are printing in duplex mode (even simulated duplex, in response to
the \.{MASTER} option of the \.{/PRINT\_MODE} qualifier), we have to arrange
to leave a page blank if two \\{recto} or two \\{verso} pages follow each
other. This information will already have been put into |this_page_recto|,
when the paper tray was selected.
For efficiency reasons, when printing in a duplex mode on the DEClaser~2200
(LN06), we switch into simplex mode for a \\{recto} page which is followed
by another such: this information was recorded in the array |blank_follows|
when the starting page was being located. This speeds up processing,
because the printer doesn't need to perform the mechanical reversal of the
page only to find that is has nothing to be printed upon it.
@<Skip to a \\{recto} page, if necessary@>=
@<Reset duplexing, if appropriate@>;
if print_mode>=true_duplex_normal then
begin
if last_page_recto=this_page_recto then
begin
if (print_mode=ln03_master) or not this_page_recto then
write(ln3_file,' ',FF) {Eject an extra page on LN03, or make
\\{recto} page blank}
end else
if (print_mode<ln03_master) and (ptr_blanks>0) and
duplex_by_page_numbers and this_page_recto then
if blank_follows[ptr_blanks]=cur_loc_after_bop then
begin {This page has blank \\{verso} side}
write(ln3_file,csi,true_simplex_normal:1,' x');
duplex_to_be_reset:=true;
decr(ptr_blanks)
end
end;
last_page_recto:=this_page_recto;
@ If the previous page was printed in simplex mode, because it was a
\\{recto} page to be followed by another one then when we meet that next
\\{recto} page we have a look to see if the following page is yet another
\\{recto} one. If not, we have to reset the required duplexing mode.
@<Reset duplexing, if appropriate@>=
if duplex_to_be_reset then {previous page was temporary excursion into
simplex mode}
if ptr_blanks>0 then
if blank_follows[ptr_blanks]=cur_loc_after_bop then
begin
decr(ptr_blanks); {and remain in simplex mode}
if print_mode>=duplex_master_normal then
write(ln3_file,' ',FF) {eject blank page for \\{verso} master}
end else
begin
write(ln3_file,csi,print_mode:1,' x');
duplex_to_be_reset:=false
end
@ When we come to the end of the document when printing in any duplexing
mode, we may just have printed a \\{recto} page; we must either eject the
second side as blank (in true duplex modes) or print an extra blank page (in
any of the master modes). This is accomplished by printing a form-feed, and
following it with a space; the form-feed on its own would just result in the
printer symbiont suppressing that which it would have inserted, but the
space fools it into sending a second form-feed itself.
@<Conditionally eject last page@>=
if (print_mode>=true_duplex_normal) and last_page_recto then
if (print_mode>=duplex_master_normal) or not duplex_to_be_reset then
write(ln3_file,FF,' ')
@ Here are the variables required to control \\{recto}/\\{verso} printing
@<Glob...@>=
@!last_page_recto:boolean; {|true| if previous page was \\{recto}}
@!this_page_recto:boolean; {used to save multiple evaluation of |recto_page|}
@!pages_printed:integer; {counts each page generated for
\.{/noduplex\_by\_page\_numbers}}
@!duplex_to_be_reset:boolean; {Used to reset any duplex mode after one sheet
has been printed in simplex}
@!cur_loc_after_bop:integer; {Records |cur_loc| after the current page's
|bop| command was processed}
@ These variables have to be initialized, of course
@<Set initial...@>==
pages_printed:=0; last_page_recto:=false; duplex_to_be_reset:=false;
@ When we first start writing the output file, we have to add commands to
tell the DEClaser printers from which tray to take the first sheet of paper,
and which printing mode to use. The control sequence ending in |' x'| is
the |DECSPDM| (Set Duplex Print Mode) command.
@<Select printing mode and feed tray on DEClaser printers@>=
if first_tray<>default_tray then
select_tray(first_tray);
if (print_mode>default_print_mode) and (print_mode<ln03_master) then
write_ln(ln3_file,csi,print_mode:1,' x');
@*Procedures for recognizing and manipulating strings. We have a
requirement for recognizing various ASCII strings in this program. Some of
these strings specify functionality required from a \.{\BS special} command,
whilst others are the two-letter abbreviations used to specify the units of
dimensions: such dimensions can appear within \.{\BS special} commands and
also in some command line qualifiers.
The following data structures support the recognition of the strings used:
for commands, we need the following texts:
\yskip\hang\.{sx\ } ``Old-style'' introducer for inclusion of an LN03 sixel
file within the program's output. Note that support for this command will
be removed in a future release following V4.0.
\yskip\hang\.{landscape} Output the following information in landscape orientation.
\yskip\hang\.{portrait} Output the following information in portrait orientation.
\yskip\hang\.{ln03:} New-style introducer for all LN03-specific \.{\BS
special}s. All the remaining command strings are to be found following this
introducer.
\yskip\hang\.{sixel\ } Command to include a pixel file into the program's
output.
\yskip\hang\.{plotfile\ } Alternative name to include a sixel file.
\yskip\hang\.{defpoint\ } Define the coordinates of a point on the page:
used in conjunction with \.{connect} to draw changebars.
\yskip\hang\.{connect\ } Draw a changebar line between a pair of previously
defined points.
\yskip\hang\.{resetpoints\ } Clear all recorded details for a specified
range of named points.
\noindent Dimensions recognized are as in \TeX, with the addition of \.{px}
(representing a dimension unit of one pixel) and \.{mi} (representing one
micrometre or micron).
The creation of these strings could have been much cleaner through use of
{W\sc EB}'s string pool feature, but it was felt that it was nevertheless
better to avoid the use of the auxiliary \.{.POOL} file.
@<Types...@>=
@!pool_pointer = 0..pool_size;
@!str_number = 0..max_strings;
@ @<Globals...@>=
@!str_pool : packed array [pool_pointer] of ASCII_code;
@!str_start : array [str_number] of pool_pointer;
@!pool_ptr : pool_pointer; {First unused position in |str_pool|}
@!str_ptr : str_number; {Number of current string being created}
@ Here's how we initialize all the strings for recognition. Note that
string $0$ is reserved for the null string. The macro definitions allow us
to refer to the various strings symbolically; note that the related numbers
will need to be changed if the order of initialization below is altered!
|min_string_match| specifies the minimum number of characters which are
required to match when the string being compared has been specified in
truncated form; at present, all strings are unique in their first two
characters.
@d min_string_match=2
@#
@d dimen_pt=1
@d dimen_mm=dimen_pt+1
@d dimen_in=dimen_mm+1
@d dimen_px=dimen_in+1
@d dimen_cm=dimen_px+1
@d dimen_pc=dimen_cm+1
@d dimen_bp=dimen_pc+1
@d dimen_dd=dimen_bp+1
@d dimen_cc=dimen_dd+1
@d dimen_sp=dimen_cc+1
@d dimen_mi=dimen_sp+1
@d spec_sx=dimen_mi+1
@d spec_land=spec_sx+1
@d spec_port=spec_land+1
@d spec_ln=spec_port+1
@d spec_six=spec_ln+1
@d spec_plt=spec_six+1
@d spec_set=spec_plt+1
@d spec_con=spec_set+1
@d spec_rst=spec_con+1
@<Set initial...@>=
pool_ptr := 0; str_ptr := 0; str_start[0] := 0;
make_string('pt');
make_string('mm');
make_string('in');
make_string('px');
make_string('cm');
make_string('pc');
make_string('bp');
make_string('dd');
make_string('cc');
make_string('sp');
make_string('mi');
make_string('sx ');
make_string('landscape');
make_string('portrait');
make_string('ln03:');
make_string('sixel ');
make_string('plotfile ');
make_string('defpoint ');
make_string('connect ');
make_string('resetpoints ');
incr(str_ptr); str_start[str_ptr]:=pool_ptr;
@ Here's the procedure for putting the strings into the pool. We make use
here of a couple of auxiliary macros for adding characters to the string
pool, and another for reporting errors with the arguments of a \.{\BS
special}.
@d print_spec == for spec_ptr:= 1 to spec_len-1 do
begin print(xchr[spec_par[spec_ptr]]);
write(term_out,xchr[spec_par[spec_ptr]])
end;
print(''''); write(term_out,'''')
@d spec_error(#) == begin error(#,' `'); print_spec end
@d append_char(#) == {Put |ASCII_code| \# at the end of the |str_pool|}
begin
str_pool[pool_ptr] := #;
incr(pool_ptr)
end
@d str_room(#) == {make sure that |str_pool| can take a string of length \#}
begin
if pool_ptr + # > pool_size then
abort('Internal error (String pool overflow)')
@:fatal error Internal error String pool overflow}{\qquad\.{(String pool overflow)}@>
end
@<Procedures for...@> =
procedure make_string(@!s : packed array [@!low..@!high : integer] of char);
var
@!i : integer;
begin
str_room(high-low+1);
incr(str_ptr);
str_start[str_ptr] := pool_ptr;
for i := low to high do append_char(xord[s[i]]);
end;
@ This module has a set of functions (and one procedure) in it that are
generally used in decoding \.{\BS special} commands. All these procedures,
together with those in the following two sections, are ``low-level'' and
included at an early point in the \PASCAL\ so that they are available to all
routines provided through {W\mc EB}'s \.{@@p} directive.
The functions are:
\item{$\bullet$} |rd_char|, which reads a character from |spec_par| at
|spec_ptr|,
\item{$\bullet$} |rd_nb_char|, which reads a non-blank character, using
|rd_char|,
\item{$\bullet$} |rd_int|, which reads an integer, using the two |rd_char|s,
\item{$\bullet$} |rd_real|, which reads a `real' number, but without exponent.
All the functions return a |boolean|, which is |true| if they `failed' (which,
in the case of the |rd_char|s, means they got to the end of the line).
|rd_real| accepts |'.'| as `0.0' (!). (Personally, I wouldn't give you twopence
for these number routines: they have several curious properties, including the
pleasing way they collapse when presented with an isolated minus-sign!)
[This comment was made by Robin Fairbairns
@^Fairbairns, Robin@>
of LaserScan (Cambridge UK), who wrote them and provided the other features
that support \.{\\changebar} commands through appropriate \.{\\special}
strings.]
The one procedure in the module is |bksp_char|, which backspaces by one
character, except, of course, at the beginning of the buffer.
@<Additional low...@>=
function rd_char (var ch : [volatile] ASCII_code) : boolean;
begin
if spec_ptr>spec_len
then begin ch:=" "; rd_char:=true; end
else begin ch:=spec_par[spec_ptr]; incr(spec_ptr); rd_char:=false; end;
end;
function rd_nb_char (var ch : [volatile] ASCII_code) : boolean;
var
@! end_of_line : boolean;
begin
repeat end_of_line := rd_char(ch) until end_of_line or (ch<>" ");
rd_nb_char := end_of_line;
end;
procedure bksp_char;
begin
if spec_ptr>1 then decr(spec_ptr);
end;
function rd_int ( var res : [volatile] integer ) : boolean;
var
@!ch : ASCII_code;
@!end_of_line,negative : boolean;
begin
res := 0; negative := false;
end_of_line:=rd_nb_char(ch);
if ch="-" then begin
negative := true;
end_of_line := rd_char(ch);
end;
if end_of_line then rd_int:=true else rd_int:=not (ch in ["0".."9"]);
while (not end_of_line) and (ch in ["0".."9"]) do begin
res:=res*10+(ch-"0");
end_of_line:=rd_char(ch);
end;
if not end_of_line then bksp_char;
if negative then res:=-res;
end;
function rd_real (var res : [volatile] real ) : boolean;
var
@!ch : ASCII_code;
@!end_of_line,negative:boolean;
@!in_fract:boolean;
@!fract:real;
@!dec_pt:real;
begin
res:=0.0; fract:=0.0; dec_pt:=1.0; negative := false;
end_of_line:=rd_nb_char(ch);
if ch="-" then begin
negative := true;
end_of_line := rd_char(ch);
end;
if end_of_line then rd_real:=true
else begin
rd_real:=false; {initial estimate}
if ch in ["0".."9"]
then in_fract:=false
else if ch="."
then begin
in_fract:=true;
end_of_line:=rd_char(ch);
end
else rd_real:=true;
end;
while (not end_of_line) and (ch in ["0".."9"])
do begin
if in_fract then begin
dec_pt:=dec_pt*0.1;
fract:=fract+dec_pt*(ch-"0");
end
else res:=res*10.0+(ch-"0");
end_of_line:=rd_char(ch);
if not(end_of_line or in_fract) then
if ch="." then begin
in_fract:=true;
end_of_line:=rd_char(ch);
end;
end;
if not end_of_line then bksp_char;
if negative then res:=-res-fract else res:=res+fract;
end;
@ Here is a function which matches characters from the buffer into which
the string associated with the \.{\BS special} command is read against those
strings in the |str_pool|. It returns the index number of that particular
string. It is not necessary for the whole stored string to be matched; it
will be noted that all strings are unique in their first two characters;
provided at least these characters are matched \&{and} the first non-matched
character in the \.{\BS special} command is a space character, then a match
will still be found. The function returns a pointer to string $0$ (the null
string) if no match is found.
Here is a macro which allows us to find the length of a string in the pool:
@d str_length(#) == (str_start[#+1]-str_start[#])
@<Additional low...@>=
function find_match : str_number;
var
@!i,@!l : integer;
@!m : str_number;
@!chr_ptr : pool_pointer;
@!eol,@!found : boolean;
begin
eol := false; {We start by skipping over any leading spaces}
while (spec_par[spec_ptr] = " ") and not eol do
if spec_ptr >= spec_len then
eol := true
else
incr(spec_ptr);
m := 0; {Assume the worst!}
if eol then spec_error('Unsupported or malformed \special ')
@:Error: Unsupported or malformed special}{\quad\.{Unsupported or malformed \\special}@>
else
begin
i := spec_ptr; {Point into the body of the \.{\BS special} command}
m := dimen_pt;
found := false;
repeat
l := str_length(m);
chr_ptr := str_start[m];
while ((spec_par[i] = str_pool[chr_ptr]) or
(spec_par[i] = str_pool[chr_ptr]+"A"-"a")) and
not eol and not found do
begin
decr(l);
if l=0 then found := true else
begin
if spec_ptr >= spec_len then eol := true
else
incr(i);
incr(chr_ptr);
end;
end;
if eol or (spec_par[i] = " ") then
if i-spec_ptr > min_string_match then found := true;
if not found then
begin
i := spec_ptr; incr(m);
end
until found or (m > str_ptr);
if not found then m := 0
else spec_ptr:=i+1;
end;
find_match := m;
end;
@ This module defines a `derived' function for reading, as an integer number of
pixels, a `dimension' from the \.{\BS special}'s arguments (where `dimension'
is---roughly---what one gets in \TeX). It firstly reads a \meta{real number}
from the argument string, and then multiplies this by an appropriate factor
determined by the following two character string, which should be one of \TeX's
\meta{physical unit} designators (\.{pt}, \.{bp}, etc) with the addition of
\.{px}, denoting pixels, and \.{mi} for microns ($\mu\hbox{m}$).
The function has a |boolean| result, which once again is |true| if it fails to
find what was expected.
@d spec_warning(#)==
begin
warning(#,' `'); print_spec
end
@<Additional low...@>=
function rd_dimension ( var res : [volatile] integer ) : boolean;
var
@! dimen_found : str_number;
@! real_value : real;
begin
if rd_real(real_value) then rd_dimension:=true
else begin
rd_dimension:=false;
dimen_found := find_match;
case dimen_found of
dimen_pt: real_value:=real_value*resolution/72.27;
dimen_in: real_value:=real_value*resolution;
dimen_px: do_nothing;
dimen_pc: real_value:=real_value*12.0*resolution/72.27;
dimen_cm: real_value:=real_value*resolution/2.54;
dimen_mm: real_value:=real_value*resolution/25.4;
dimen_bp: real_value:=real_value*resolution/72.0;
dimen_dd: real_value:=real_value*(1238.0/1157.0)*resolution/72.27;
dimen_cc: real_value:=real_value*(14856.0/1157.0)*resolution/72.27;
dimen_mi: real_value:=real_value*resolution/25400.0;
dimen_sp: real_value:=real_value*resolution/65536.0/72.27;
othercases
begin
spec_warning('Missing or unknown unit of measure:');
@:Warning: Missing or unknown unit of measure}{\quad\.{Missing or unknown unit of measure}@>
rd_dimension:=true; {Assumes value is in pixels}
end;
endcases;
end;
res := round(real_value);
end;
@ This analogous function reads a dimension and converts it to scaled
points; this is used by the \.{/hfuzz} and \.{/vfuzz} qualifiers.
@<Additional low...@>=
function rd_scaled_pt ( var res : [volatile] integer ) : boolean;
var
@! dimen_found : str_number;
@! real_value : real;
begin
if rd_real(real_value) then rd_scaled_pt:=true
else begin
rd_scaled_pt:=false;
dimen_found := find_match;
case dimen_found of
dimen_pt: real_value:=real_value*65536.0;
dimen_in: real_value:=real_value*65536.0*72.27;
dimen_px: real_value:=real_value/conv;
dimen_pc: real_value:=real_value*12.0*65536.0;
dimen_cm: real_value:=real_value*65536.0*72.27/2.54;
dimen_mm: real_value:=real_value*65536.0*72.27/25.4;
dimen_bp: real_value:=real_value*65536.0*72.27/72.0;
dimen_dd: real_value:=real_value*(1238.0/1157.0)*65536.0;
dimen_cc: real_value:=real_value*(14856.0/1157.0)*65536.0;
dimen_mi: real_value:=real_value*65536.0*72.27/25400.0;
dimen_sp: do_nothing;
othercases
begin
spec_warning('Missing or unknown unit of measure:');
@:Warning: Missing or unknown unit of measure}{\quad\.{Missing or unknown unit of measure}@>
rd_scaled_pt:=true; {Assumes value is in sp}
end;
endcases;
end;
res := round(real_value);
end;
@ Collection of rules for a page is fairly trivial, but it's now called from
three places, so we make it a procedure. All rules are collected and placed
into |page_rules|. Later, the rules will be merged.
@p procedure Collect_rule(hhh, vvv, wwidth, hheight : integer);
begin
if rules_on_page<max_rules then incr(rules_on_page) else
if not rule_limit_exceeded then begin
warning('more than ',max_rules:1,' rules on page; excess ignored!');
@.more than ... rules@>
rule_limit_exceeded:=true end;
with page_rules[rules_on_page] do begin
horiz := hhh; vert := vvv; height := hheight;
width := wwidth; covered := false;
end;
end;
@ This module defines three procedures:
\yskip\hang$\bullet$ |reset_points|, which resets the |present| flags of a
range of |point|s,
\yskip\hang$\bullet$ |define_point|, which gives a value to a |point|, and
\yskip\hang$\bullet$ |connect_points|, which makes connection between two
|point|s.
The macro simplifies the task of finding the next comma in the \.{\BS
special} string.
@d find_comma == repeat end_of_line:=rd_nb_char(ch)
until end_of_line or (ch<>",")
@p procedure reset_points;
var
@!low,high : integer;
begin
rd_int(low); rd_int(high);
if (0<low) and (low<=high) and (high<=max_points)
then begin
for k:=low to high do point[k].present:=false;
end
else if (0<low) and (low<=max_points)
then point[low].present:=false;
end;
procedure define_point;
var
@! which : integer; {the point number}
@! h_val, v_val : integer; {its coordinates}
@! ch : ASCII_code;
@! end_of_line : boolean;
begin
if rd_int(which) then
spec_error('Point number missing in \special ')
@:Error: Point number missing in special}{\quad\.{Point number missing in \\special}@>
else if (which<=0) or (which>max_points) then
spec_error('Invalid point number in \special ')
@:Error: Invalid point number in special}{\quad\.{Invalid point number in \\special}@>
else begin
find_comma;
if ch="("
then begin
if rd_dimension(h_val)
then begin
h_val:=hh;
v_val:=vv;
end
else begin
if rd_nb_char(ch) then do_nothing;
if ch=","
then begin
if rd_dimension(v_val) then v_val:=vv;
end
else v_val:=vv;
end;
end
else
begin
h_val:=hh; v_val:=vv;
end;
{now we've got |h_val|, |v_val| and |which| all set (to something!)}
with point[which] do
begin hh := h_val; vv := v_val; present := true end;
end;
end;
procedure connect_points;
var
@! a1,b1, a2,b2 : integer;
@! width : integer;
@! half_width : integer;
@! end_of_line, bad_point : boolean;
@! ch : ASCII_code;
begin
end_of_line:=rd_int(a1);
end_of_line:=rd_nb_char(ch);
if ch="/"
then end_of_line:=rd_int(b1)
else begin
if not end_of_line then bksp_char; b1:=a1;
end;
find_comma;
if not end_of_line then bksp_char;
end_of_line:=rd_int(a2);
end_of_line:=rd_nb_char(ch);
if ch="/"
then end_of_line:=rd_int(b2)
else begin
if not end_of_line then bksp_char; b2:=a2;
end;
find_comma;
if not end_of_line then bksp_char;
if rd_dimension(width) then width:=2; {default dimension is 2 pixels, ~0.4 pts}
if odd(count[0])
then begin b1:=a1; b2:=a2; end;
{check the point numbers}
if (b1<=0) or (b1>max_points) or (b2<=0) or (b2>max_points)
then bad_point:=true
else bad_point:=false;
if not bad_point then begin
if (not point[b1].present) or (not point[b2].present)
then bad_point:=true;
end;
if bad_point then
spec_error('Invalid point number in \special ')
@:Error: Invalid point number in special}{\quad\.{Invalid point number...}@>
else begin
half_width := width div 2;
if point[b1].hh=point[b2].hh
then Collect_rule(point[b1].hh-half_width,
VAX_max(point[b1].vv,point[b2].vv),
width,
abs(point[b2].vv-point[b1].vv))
else if point[b1].vv=point[b2].vv
then Collect_rule(VAX_min(point[b1].hh,point[b2].hh),
point[b1].vv+half_width,
abs(point[b1].hh-point[b2].hh),
width)
else spec_error('Diagonal rule in \special ');
@:Error: Diagonal rule in special}{\quad\.{Diagonal rule in \\special}@>
end;
end;
@* Translation to symbolic form.
The main work of \.{DVItoLN03} is accomplished by the |do_page| procedure,
which produces the output for an entire page, assuming that the |bop|
command for that page has already been processed. This procedure is
essentially an interpretive routine that reads and acts on the \.{DVI}
commands.
@ The definition of \.{DVI} files refers to six registers,
$(h,v,w,x,y,z)$, which hold integer values in \.{DVI} units. In practice,
we also need registers |hh| and |vv|, the pixel analogs of $h$ and $v$,
since it is not always true that |hh=pixel_round(h)| or
|vv=pixel_round(v)|. Note that the current font is neither stacked
nor restored by |push| and |pop| commands.
The stack of $(h,v,w,x,y,z)$ values is represented by eight arrays
called |hstack|, \dots, |zstack|, |hhstack|, and |vvstack|.
@<Glob...@>=
@!h,@!v,@!w,@!x,@!y,@!z,@!hh,@!vv:integer; {current state values}
@!hstack,@!vstack,@!wstack,@!xstack,@!ystack,@!zstack:
array [0..stack_size] of integer; {pushed down values in \.{DVI} units}
@!hhstack,@!vvstack:
array [0..stack_size] of integer; {pushed down values in pixels}
@ Three characteristics of the pages (their |max_v|, |max_h|, and
|max_s|) are specified in the postamble, and a warning message
is printed if these limits are exceeded. Actually |max_v| is set to
the maximum height plus depth of a page, and |max_h| to the maximum width,
for purposes of page layout. Since characters can legally be set outside
of the page boundaries, it is not an error when |max_v| or |max_h| is
exceeded. But |max_s| should not be exceeded.
The postamble also specifies the total number of pages; \.{DVItoLN03}
checks to see if this total is accurate.
@<Glob...@>=
@!max_v:integer; {the value of |abs(v)| should probably not exceed this}
@!max_h:integer; {the value of |abs(h)| should probably not exceed this}
@!max_h_saved,@!max_v_saved:integer; {`Static' value for oversize messages}
@!max_s:integer; {the stack depth should not exceed this}
@!max_v_so_far,@!max_h_so_far,@!max_s_so_far:integer; {the record high levels}
@!total_pages:integer; {the stated total number of pages}
@!page_count:integer; {the total number of pages seen so far}
@ @<Set init...@>=
max_v:=@'17777777777-99; max_h:=@'17777777777-99; max_s:=stack_size+1;@/
max_v_so_far:=0; max_h_so_far:=0; max_s_so_far:=0; page_count:=0;
@ Here is a procedure which will be required when decoding |xxx| commands.
If the string of characters in a \TeX\ \.{\\special} command commences with
the character string |'SX '| (i.e., `old style'), or |'ln03:plotfi[le] '| or
|'ln03:sixel '| (`new style'), the following procedure will copy the
remainder of the \.{\\special} command's parameter string (which is assumed
to be the name of a VAX/VMS file containing a graphics image encoded into
DEC's standard ``sixel'' format) into the |text| field of a |segment|; the
remaining fields are used to record the page co\"ordinates at which the dump
shall be placed.
@p procedure include_sixel_dump;
begin
finish_seg; {ensure that we have no |cur_seg|}
new_segment(true,true,0,0); {|font_code| and |ch_code| irrelevant}
with cur_seg^ do
begin
seg_type:=sixel_dump; {that's the flavour of segment}
text:='';
while spec_ptr <= spec_len do
begin
text := text + xchr[spec_par[spec_ptr]];
incr(spec_ptr)
end;
xs:=x_pos; {record starting position of dump's placement}
end;
finish_seg {detach the segment; it's attached to correct |seg_list|}
end;
@ Before we get into the details of |do_page|, it is convenient to
consider a simpler routine that computes the first parameter of each
opcode.
@p function first_par(o:eight_bits):integer;
begin case o of
sixty_four_cases(set_char_0),sixty_four_cases(set_char_0+64):
first_par:=o-set_char_0;
set1,put1,fnt1,xxx1,fnt_def1: first_par:=get_byte;
set1+1,put1+1,fnt1+1,xxx1+1,fnt_def1+1: first_par:=get_two_bytes;
set1+2,put1+2,fnt1+2,xxx1+2,fnt_def1+2: first_par:=get_three_bytes;
right1,w1,x1,down1,y1,z1: first_par:=signed_byte;
right1+1,w1+1,x1+1,down1+1,y1+1,z1+1: first_par:=signed_pair;
right1+2,w1+2,x1+2,down1+2,y1+2,z1+2: first_par:=signed_trio;
set1+3,set_rule,put1+3,put_rule,right1+3,w1+3,x1+3,down1+3,y1+3,z1+3,
fnt1+3,xxx1+3,fnt_def1+3: first_par:=signed_quad;
nop,bop,eop,push,pop,pre,post,post_post,undefined_commands: first_par:=0;
w0: first_par:=w;
x0: first_par:=x;
y0: first_par:=y;
z0: first_par:=z;
sixty_four_cases(fnt_num_0): first_par:=o-fnt_num_0;
end;
end;
@ Here is another subroutine that we need: It computes the number of
pixels in the height or width of a rule. Characters and rules will line up
properly if the sizes are computed precisely as specified here. (Since
|conv| is computed with some floating-point roundoff error, in a
machine-dependent way, format designers who are tailoring something for a
particular resolution should not plan their measurements to come out to an
exact integer number of pixels; they should compute things so that the
rule dimensions are a little less than an integer number of pixels, e.g.,
4.99 instead of 5.00.)
@p function rule_pixels(x:integer):integer;
{computes $\lceil|conv|\cdot x\rceil$}
var n:integer;
begin n:=trunc(conv*x);
if n<conv*x then rule_pixels:=n+1 @+ else rule_pixels:=n;
end;
@ Strictly speaking, the |do_page| procedure is really a function with
side effects, not a `\&{procedure}'\thinspace; it returns the value |false|
if \.{DVItoLN03} should be aborted because of some unusual happening. The
subroutine is organized as a typical interpreter, with a multiway branch
on the command code followed by |goto| statements leading to routines that
finish up the activities common to different commands. We will use the
following labels:
@d fin_set=41 {label for commands that set or put a character}
@d fin_rule=42 {label for commands that set or put a rule}
@d move_right=43 {label for commands that change |h|}
@d move_down=44 {label for commands that change |v|}
@d change_font=45 {label for commands that change |cur_font|}
@ Some \PASCAL\ compilers severely restrict the length of procedure bodies,
so we shall split |do_page| into two parts, one of which is
called |special_cases|. The different parts communicate with each other
via the global variables mentioned above, together with the following ones:
@<Glob...@>=
@!s:integer; {current stack size}
@!cur_font:integer; {current internal font number}
@!cur_base:integer; {$\equiv$|glyph_base[cur_font]|, to index into |glyphs|}
@ Here is the overall setup. The parameter is an extension from the
original program to accommodate virtual font character sequences; in normal
operation (when processing bytes from the \.{DVI} file), this parameter is
negative, and |do_page| processes one complete page of the file. If a
character being set on the current page is taken from a virtual font, and
doesn't simply map to a single physical character from some other font, then
|do_page| will be called recursively, with a parameter that points to the
first byte of a sequence of |dvi| bytes which implements the virtual
character.
@p @t\4@>@<Declare the function called |special_cases|@>@;
@t\4@>@<Declare the function called |reduce_rules|@>@;
function do_page; {|do_page(@!vf_start:integer):boolean;|}
label fin_set,fin_rule,move_right,done,9998,9999;
var o:eight_bits; {operation code of the current command}
@!p,@!q:integer; {parameters of the current command}
@!a:integer; {byte number of the current command}
@!hhh:integer; {|h|, rounded to the nearest pixel}
@!saved_font:integer; {implicit |push| around virtual font character}
@!old_take:integer; {ditto for taking |dvi| from array |vf|}
begin
if vf_start < 0 then
begin {initialize the state variables if not recursing}
s:=0; h:=0; v:=0; w:=0; x:=0; y:=0; z:=0; hh:=0; vv:=0;
end;
saved_font:=cur_font; {for later restoration}
cur_font:=nf; {set current font undefined}
old_take:=vf_take;
vf_take:=vf_start; {ensure |dvi| bytes taken from appropriate source}
while true do @<Translate the next command in the \.{DVI} file;
|goto 9999| with |do_page=true| if it was |eop|;
|goto 9998| if premature termination is needed@>;
9998: print_ln('!'); do_page:=false;
9999: cur_font:=saved_font;
cur_base:=glyph_base[cur_font];
vf_take:=old_take;
end;
@ Error messages are written to the log file with full details of their position
within the \.{DVI} file, and, in a shorter form, to the terminal.
@<Translate the next command...@>=
begin a:=cur_loc;
o:=get_byte; p:=first_par(o);
if eof(dvi_file) then bad_dvi('the file ended prematurely');
@:Bad DVI file the file ended prematurely}{\quad\.{the file ended prematurely}@>
@<Start translation of command |o| and |goto| the appropriate label to
finish the job@>;
fin_set: @<Finish a command that either sets or puts a character, then
|goto move_right| or |done|@>;
fin_rule: @<Finish a command that either sets or puts a rule, then
|goto move_right| or |done|@>;
move_right: @<Finish a command that sets |h:=h+q|, then |goto done|@>;
done:
end
@ The multiway switch in |first_par|, above, was organized by the length
of each command; the one in |do_page| is organized by the semantics.
@<Start translation...@>=
if o<set_char_0+128 then @<Translate a |set_char| command@>
else case o of
four_cases(set1): goto fin_set;
four_cases(put1): goto fin_set;
set_rule: goto fin_rule;
put_rule: goto fin_rule;
@t\4@>@<Cases for commands |nop|, |bop|, \dots, |pop|@>@;
@t\4@>@<Cases for horizontal motion@>@;
othercases if special_cases(o,p,a) then goto done@+else goto 9998
endcases
@* Rule Accumulation.
We accumulate all rules generated on a page. At the end of the page,
the rules are combined to form larger rules when possible, then
output.
A special record describes the rule rectangles encountered.
@<Types...@>=
@!rules = record {rule record}
@!horiz,@; {Horizontal Coordinate}
@!width,@; {Width in Horizontal Direction}
@!vert,@; {Vertical Coordinate}
@!height:integer; {Height in Vertical Direction}
@!covered:boolean; {Indicates if combined}
end;
@ An array of |rules| is defined along with an index.
@<Glob...@>=
@!page_rules:array[1..max_rules] of rules; {rules generated on this page}
@!rules_on_page: 0..max_rules; {count of rules found on this page}
@!rule_limit_exceeded: boolean; {used to produce just one warning message}
@ Initialize |rules_on_page| to show no rules yet.
@<Set init...@>=
@!rules_on_page := 0; {set no rules found yet}
@!rule_limit_exceeded := false; {so we haven't got too many, either!}
@ This module used to contain the code for collecting rules, but now it's
limited to calling a procedure elsewhere that does the job.
@<Collect Rules for Page@>=
Collect_rule(hh, vv, rule_pixels(q), rule_pixels(p));
@ Rules can now be combined and output in the \.{LN3} file. To accomplish
this the rules are scanned in vertical and then in horizontal order. During
the scan adjacent rules are combined and marked as used. At completion, rules
that aren't marked as |covered| are output.
@<Combine and output Rules@>=
if rules_on_page > 0 then
begin
reduce_rules;
for rules_on_page := rules_on_page downto 1 do
with page_rules[rules_on_page] do
if not covered then
write_ln(ln3_file,csi,'1;',horiz+left_marg:1,';',
vert-height+top_marg:1,';',
height:1,';',width:1,'!|');
rules_on_page := 0
end;
@ |reduce_rules| implements the rule reduction algorithm. Maps are generated
for traversing all rules both horizontally and vertically. Rules can overlap
or underlap by up to |max_lap_rules| in |height| or |width|.
@<Declare the function called |reduce_rules|@>=
procedure reduce_rules;
var
horiz_map: array[1..max_rules] of 0..max_rules;
verti_map: array[1..max_rules] of 0..max_rules;
hi,hj,vi,vj: 0..max_rules;
ht,vt:integer;
begin
for hi := 1 to rules_on_page do horiz_map[hi] := hi;
for vi := 1 to rules_on_page do verti_map[vi] := vi;
@<Build Horizontal Map@>@;
@<Traverse Horizontally@>@;
@<Build Vertical Map@>@;
@<Traverse Vertically@>@;
end;
@ Build map for horizontal combination by sorting the rules into
increasing vertical order and within the same vertical line into
increasing horizontal order. This allows for rule combination by
looking for |width| overlap.
@<Build Horiz...@>=
for hi := 1 to rules_on_page-1 do
for hj := (hi+1) to rules_on_page do
if (page_rules[horiz_map[hi]].vert > page_rules[horiz_map[hj]].vert) or
((page_rules[horiz_map[hi]].vert = page_rules[horiz_map[hj]].vert) and
(page_rules[horiz_map[hi]].horiz > page_rules[horiz_map[hj]].horiz)) then
begin ht:=horiz_map[hi]; horiz_map[hi]:=horiz_map[hj];
horiz_map[hj]:=ht end;
@ Build map for vertical combination by sorting the rules into
increasing horizontal order and within the same horizontal line into
increasing vertical order. This allows for rule combination by
looking for |height| overlap.
@<Build Verti...@>=
for vi := 1 to rules_on_page-1 do
for vj := (vi+1) to rules_on_page do
if (page_rules[verti_map[vi]].horiz > page_rules[verti_map[vj]].horiz) or
((page_rules[verti_map[vi]].horiz = page_rules[verti_map[vj]].horiz) and
(page_rules[verti_map[vi]].vert > page_rules[verti_map[vj]].vert)) then
begin vt:=verti_map[vi]; verti_map[vi]:=verti_map[vj];
verti_map[vj]:=vt end;
@ Traverse mapped rules looking for adjoining vertical rules. We actually
look for |height| overlap within |max_lap_rules|. Rules that have been
combined with other rules are set |covered := true|.
@<Traverse Verti...@>=
for vi:=1 to rules_on_page-1 do
with page_rules[verti_map[vi]] do
if not covered then
for vj:= vi+1 to rules_on_page do
if not page_rules[verti_map[vj]].covered then
begin
vt := vert - (page_rules[verti_map[vj]].vert -
page_rules[verti_map[vj]].height);
if ((horiz = page_rules[verti_map[vj]].horiz) and
(width = page_rules[verti_map[vj]].width) and
(abs(vt) <= max_lap_rules)) then
begin
vert := page_rules[verti_map[vj]].vert;
height := height + page_rules[verti_map[vj]].height - vt;
page_rules[verti_map[vj]].covered := true;
end;
end;
@ Traverse mapped rules looking for adjoining horizontal rules. We actually
look for |width| overlap within |max_lap_rules|. Rules that have been
combined with other rules are set |covered := true|.
@<Traverse Horiz...@>=
for hi:=1 to rules_on_page-1 do
with page_rules[horiz_map[hi]] do
if not covered then
for hj:= hi+1 to rules_on_page do
if not page_rules[horiz_map[hj]].covered then
begin
ht := (horiz+width) - page_rules[horiz_map[hj]].horiz;
if ((vert = page_rules[horiz_map[hj]].vert) and
(height = page_rules[horiz_map[hj]].height) and
(abs(ht) <= max_lap_rules)) then
begin
width := width + page_rules[horiz_map[hj]].width - ht;
page_rules[horiz_map[hj]].covered := true;
end;
end;
@ @<Declare the function called |special_cases|@>=
function special_cases(@!o:eight_bits;@!p,@!a:integer):boolean;
label change_font,move_down,done,9998;
var q:integer; {parameter of the current command}
@!k:integer; {loop index}
@!bad_char:boolean; {has a non-ASCII character code appeared in this \\{xxx}?}
@!pure:boolean; {is the command error-free?}
@!vvv:integer; {|v|, rounded to the nearest pixel}
begin pure:=true;
case o of
@t\4@>@<Cases for vertical motion@>@;
@t\4@>@<Cases for fonts@>@;
four_cases(xxx1): @<Translate an |xxx| command and |goto done|@>;
pre: begin error('preamble command within a page!'); goto 9998;
end;
@.preamble command within a page@>
post,post_post: begin error('postamble command within a page!'); goto 9998;
@.postamble command within a page@>
end;
othercases begin error('undefined command ',o:1,'!');
goto done;
@.undefined command@>
end
endcases;
move_down: @<Finish a command that sets |v:=v+p|, then |goto done|@>;
change_font: @<Finish a command that changes the current font,
then |goto done|@>;
9998: pure:=false;
done: special_cases:=pure;
end;
@ If we meet a |bop|, something's gone amiss, because |do_page| should never
see one of these commands.
When |eop| is met, we have to cater for two possibilities:
\item{$\bullet$} It's the end of a page in the \.{DVI} file, in which case
we must output all the glyphs, rules and bitmaps that we've accumulated;
having done this, we reset |max_h_so_far| and |max_v_so_far| so that each
page's dimensions are considered under the same conditions.
\item{$\bullet$} It terminates a stored sequence of |dvi| bytes
corresponding to a character taken from a virtual font.
If |push| is met, we don't bleat about exceeding the maximum stack size
reported by \TeX\ if the command has been read from a virtual character
sequence.
@<Cases for commands |nop|, |bop|, \dots, |pop|@>=
nop: goto done;
bop: begin error('bop occurred before eop!'); goto 9998;
@.bop occurred before eop@>
end;
eop:if vf_take<0 then
begin
if s<>0 then error('stack not empty at end of page (level ',
s:1,')!');
@:Error: stack not empty}{\quad\.{stack not empty...}@>
if max_h<>max_h_saved then
warning('Page wider than TeX reported by ',
(max_h-max_h_saved)/65536.0:1:4,'pt');
@:Warning: Page wider than TeX reported}{\quad\.{Page wider than }\TeX\.{...}@>
if max_v<>max_v_saved then
warning('Page deeper than TeX reported by ',
(max_v-max_v_saved)/65536.0:1:4,'pt');
@:Warning: Page deeper than TeX reported}{\quad\.{Page deeper than }\TeX\.{...}@>
@<Move to top of form@>;@/
@<Combine and...@>@;
do_page:=true;@/
@<Output contents of page@>;@/
write(ln3_file,LF); {LF after page}
new_orient:=orientation;
@<Change page orientation@>; {reset, if necessary}
max_h:=max_h_saved; max_h_so_far:=max_h;
max_v:=max_v_saved; max_v_so_far:=max_v;
top_of_page:=true;
goto 9999;
end else
begin
do_page:=true;
goto 9999
end;
push: begin
if s=max_s_so_far then
begin max_s_so_far:=s+1;
if (s=max_s) and (vf_take<0) then
error('push deeper than claimed in postamble!');
@:Error: push deeper than claimed}{\quad\.{push deeper than claimed...}@>
if s=stack_size then
begin capacity_exceeded('stack size=',stack_size:1);
@:capacity exceeded stack size}{\quad\.{stack size}@>
goto 9998;
end;
end;
hstack[s]:=h; vstack[s]:=v; wstack[s]:=w;
xstack[s]:=x; ystack[s]:=y; zstack[s]:=z;
hhstack[s]:=hh; vvstack[s]:=vv; incr(s); goto done;
end;
pop: begin
if s=0 then error('pop illegal at level zero')
@:Error: pop illegal at level zero}{\quad\.{pop illegal at level zero}@>
else begin decr(s);
if (vv <> vvstack[s]) and (vvstack[s]>=0) then finish_seg;
{different row of pixels}
hh:=hhstack[s]; vv:=vvstack[s];
h:=hstack[s]; v:=vstack[s]; w:=wstack[s];
x:=xstack[s]; y:=ystack[s]; z:=zstack[s];
end;
goto done;
end;
@ Rounding to the nearest pixel is best done in the manner shown here, so as
to be inoffensive to the eye: When the horizontal motion is small, like a
kern, |hh| changes by rounding the kern; but when the motion is large, |hh|
changes by rounding the true position |h| so that accumulated rounding errors
disappear. We allow a larger space in the negative direction than in
the positive one, because \TeX\ makes comparatively
large backspaces when it positions accents.
@d out_space==if (p>=font_space[cur_font])or(p<=-4*font_space[cur_font]) then
hh:=pixel_round(h+p)
else hh:=hh+pixel_round(p);
q:=p; goto move_right
@<Cases for horizontal motion@>=
four_cases(right1):begin out_space @+end;
w0,four_cases(w1):begin w:=p; out_space @+end;
x0,four_cases(x1):begin x:=p; out_space @+end;
@ Vertical motion is done similarly, but with the threshold between
``small'' and ``large'' increased by a factor of five. The idea is to make
fractions like ``$1\over2$'' round consistently, but to absorb accumulated
rounding errors in the baseline-skip moves.
@d out_vmove==if abs(p)>=5*font_space[cur_font] then vv:=pixel_round(v+p)
else vv:=vv+pixel_round(p);
finish_seg;
goto move_down
@<Cases for vertical motion@>=
four_cases(down1):begin out_vmove @+end;
y0,four_cases(y1):begin y:=p; out_vmove @+end;
z0,four_cases(z1):begin z:=p; out_vmove @+end;
@ @<Cases for fonts@>=
sixty_four_cases(fnt_num_0): goto change_font;
four_cases(fnt1): goto change_font;
four_cases(fnt_def1): begin define_font(p); goto done; end;
@ We need a certain amount of extra space in order to store the details of
\.{\BS special} commands as we work on them.
@<Glob...@>=
@!spec_par:packed array [1..max_special] of ASCII_code; {used to hold a
\.{\BS special} command}
@!spec_len: integer; {the space-stripped length of a \.{\BS special} command}
@!spec_ptr: integer; {pointer while decoding a \.{\BS special} command}
@!spec_cmd: str_number; {identifies a command string in a \.{\BS special}}
@!spec_unsupported : boolean;
@!spec_ch : ASCII_code;
@!point : array [1..max_points] of point_type;
@ This data structure records the co\"ordinates of a point generated by the
\.{defpoint} \.{\BS special}. Such co\"ordinates are recorded in LN03
dimensions.
@<Types...@>=
@!point_type = packed record
@!hh,@!vv : integer;
@!present : boolean;
end;
@ Let's preset that which we've just defined\dots
@<Preset...@>=
for k:=1 to max_points do point[k].present:=false;
@ |xxx| commands in the DVI file arise from \.{\\special} commands in the
{\TeX} source file. \.{\\special} commands may be used for a variety of
things, so we must decode them further here.
We support two sorts of \.{\\special} name. The first derives from the
original (pre-V3) of this driver, of which there is but one example,
`\.{\\special\{SX\ file-name\}}'. The second sort takes the form:\par
\leavevmode\hbox{`\.{\\special\{LN03:}\meta{function-name}[\.\ %
\meta{other-parameters}[\.\ \dots]]\.\}'},\par
\noindent as in Flavio Rose's original. The \meta{function name}s
\.{landscape} and \.{portrait} can appear with or without the \.{LN03:}
prefix We support both versions, with the addition (over early versions of
this driver) that command decoding is case-insensitive.
This module does various sanity checks on the \.{\BS special} command's
parameter, ending up with its characters in |spec_par|, the count of characters
in |spec_len|, and |spec_ptr| pointing to the first character not yet `looked
at'.
The module (if successful) calls one of the subsidiary routines
|Copy_sixel_file|, |define_point|, |connect_points| or |reset_points|, actually
to implement the \.{\BS special} command before going to |done|.
@<Translate an |xxx| command and |goto done|@>=
begin bad_char:=false;
if p<0 then error('\special string of negative length!');
@:Error: special string of negative length}{\quad\.{\\special string of negative length}@>
if p>max_special then
begin error('\special string too long!');
@:Error: special string too long}{\quad\.{\\special string too long}@>
p:=max_special
end;
for k:=1 to p do
begin q:=get_byte;
if (q<" ")or(q>"~") then bad_char:=true else
spec_par[k]:=q;
end;
{intialize the \.{\\special} decoding routines}
spec_len:=p; spec_ptr:=1;
{\dots\ and start decoding!!!}
@<Find start of \.{\\special}'s string(s)@>;
if not bad_char then begin
@<Match first command in \.{\\special}@>;
@<Match the \meta{function name} in a \.{\\special}@>;
end;
if bad_char then error('non-ASCII character in \special command!')
@:Error: non-ASCII character in special}{\quad\.{non-ASCII character in \\special}@>
else if spec_unsupported
then spec_error('Unsupported \special ');
@:Error: Unsupported special}{\quad\.{Unsupported \\special}@>
spec_unsupported:=false;
goto done;
end
@ If there's any space characters at the start of the \.{\\special}
string, we can safely skip past them\dots
@<Find start of \.{\\special}'s string(s)@>=
if rd_nb_char(spec_ch) then
begin
error('Null \special argument!');
@:Error: Null special argument}{\quad\.{Null \\special argument}@>
goto done;
end;
bksp_char
@ We have a very limited choice of commands that may be given without being
preceded by the device specifier `\.{ln03:}'\dots
@<Match first command in \.{\\special}@>=
spec_cmd := find_match;
case spec_cmd of
spec_sx: spec_cmd := spec_six; {Substitute |'ln03:sixel'| for |'sx'|}
spec_ln: spec_cmd := find_match; {Find string following |'ln03:'|}
spec_land, spec_port: do_nothing; {Recognize again in next section}
othercases
spec_cmd:=0; {Command \&{must} start with |'sx '| or |'ln03:'|!}
endcases
@ The majority of \.{\\special} \meta{function name}s will be recognized
here\dots
@<Match the \meta{function name} in a \.{\\special}@>=
case spec_cmd of
spec_land: new_orient:=landscape; {|'landscape'| or |'ln03:landscape'|}
spec_port: new_orient:=portrait; {|'portrait'| or |'ln03:portrait'|}
spec_six, spec_plt: {|'ln03:sixel'| or |'ln03:plotfile'|}
include_sixel_dump;
spec_set: {|'ln03:defpoint'|}
define_point;
spec_con: {|'ln03:connect'|}
connect_points;
spec_rst: {|'ln03:resetpoints'|}
reset_points;
othercases
spec_unsupported:=true
endcases
@ @<Translate a |set_char|...@>=
goto fin_set
@ @<Finish a command that either sets or puts a character...@>=
if p<0 then p:=255-((-1-p) mod 256)
else if p>=256 then p:=p mod 256; {width computation for oriental fonts}
@^oriental characters@>@^Chinese characters@>@^Japanese characters@>
if (p<font_bc[cur_font])or(p>font_ec[cur_font]) then q:=invalid_width
else q:=cur_font_glyph(p).width;
if q=invalid_width then
begin error('character ',p:1,' invalid in font ');
@.character $c$ invalid...@>
print_font(cur_font);
if cur_font<>nf then print('!'); {font |nf| has `\.!' in its name}
end;
if font_type[cur_font]=missing then
@<Substitute a solid rule for character from missing font@>
else
ord_text(p);
if o>=put1 then goto done;
if q=invalid_width then q:=0
else hh:=hh+cur_font_glyph(p).pixel_width;
goto move_right
@ @<Finish a command that either sets or puts a rule...@>=
q:=signed_quad;
if (p>0)and(q>0) then @<Collect Rules...@>@;
if o=put_rule then goto done;
hh:=hh+rule_pixels(q); goto move_right
@ A sequence of consecutive rules, or consecutive characters in a fixed-width
font whose width is not an integer number of pixels, can cause |hh| to drift
far away from a correctly rounded value. \.{DVItoLN03} ensures that the
amount of drift will never exceed |max_drift| pixels.
Since \.{DVItype} is intended to diagnose strange errors, it checks
carefully to make sure that |h| and |v| do not get out of range.
Normal \.{DVI}-reading programs need not do this, but \.{DVItoLN03} makes some
of these same checks and reports characters which are ``set'' outside the page
(as reported by \TeX\ itself).
No warning is given when |max_h_so_far| exceeds |max_h| by less than~100,
since 100 units is invisibly small; it's approximately the wavelength of
visible light, in the case of \TeX\ output. Rounding errors can be expected
to make |h| and |v| slightly more than |max_h| and |max_v|, every once in
a~while; hence small discrepancies are not cause for alarm.
@d infinity==@'17777777777 {$\infty$ (approximately)}
@d max_drift=2 {we insist that abs|(hh-pixel_round(h))<=max_drift|}
@<Finish a command that sets |h:=h+q|, then |goto done|@>=
if (h>0)and(q>0) then if h>infinity-q then
begin error('arithmetic overflow! parameter changed from ',@|
@.arithmetic overflow...@>
q:1,' to ',infinity-h:1);
q:=infinity-h;
end;
if (h<0)and(q<0) then if -h>q+infinity then
begin error('arithmetic overflow! parameter changed from ',@|
q:1,@| ' to ',@|(-h)-infinity:1);
q:=(-h)-infinity;
end;
@<Record current position of any text output@>;
hhh:=pixel_round(h+q);
if abs(hhh-hh)>max_drift then
if hhh>hh then hh:=hhh-max_drift
else hh:=hhh+max_drift;
h:=h+q;
if abs(h)>max_h_so_far then
begin if abs(h)>max_h+h_fuzz then
max_h:=abs(h);
max_h_so_far:=abs(h);
end;
goto done
@ If the |cur_font| wasn't available for downloading to the LN03, instead of
imaging the glyph we output a solid rule of the dimensions given by the
\.{TFM} file (on the proviso that the \.{DVI} file couldn't have been
generated by \TeX\ if the font was totally unknown).
@<Substitute a solid rule for character from missing font@>=
with cur_font_glyph(p) do
Collect_rule(hh,vv+pixel_depth,pixel_width,pixel_height+pixel_depth)
@ Procedure |do_page| gets to label |move_right| to record the rightmost
excursion of any character that's just been set: however, with virtual
fonts, we can end up with |cur_seg=nil|.
@<Record current position of any text output@>=
if ((o<set_char_0+128) or
((o>=set1) and (o<set1+4)) or
((o>=put1) and (o<put1+4))) and
(cur_seg<>nil) then
cur_seg^.xe:=x_pos;
@ @<Finish a command that sets |v:=v+p|, then |goto done|@>=
if (v>0)and(p>0) then if v>infinity-p then
begin error('arithmetic overflow! parameter changed from ',
@.arithmetic overflow...@>
p:1,' to ',infinity-v:1);
p:=infinity-v;
end;
if (v<0)and(p<0) then if -v>p+infinity then
begin error('arithmetic overflow! parameter changed from ',
p:1, ' to ',(-v)-infinity:1);
p:=(-v)-infinity;
end;
vvv:=pixel_round(v+p);
if abs(vvv-vv)>max_drift then
if vvv>vv then vv:=vvv-max_drift
else vv:=vvv+max_drift;
v:=v+p;
if abs(v)>max_v_so_far then
begin if abs(v)>max_v+v_fuzz then
max_v:=abs(v);
max_v_so_far:=abs(v);
end;
goto done
@ @<Finish a command that changes the current font...@>=
font_num[nf]:=p; cur_font:=0;
while font_num[cur_font]<>p do incr(cur_font);
if cur_font=nf then
error('reference to an unloaded font');
@:Error: reference to unloaded font}{\quad\.{reference to unloaded font}@>
cur_font:=font_map[cur_font]; cur_base:=glyph_base[cur_font];
finish_seg;
goto done
@* Font usage pass. Whilst gathering the font usage statistics, each |bop|
command will cause various variables to be updated which the ``real'' pass
through the file would not expect to have changed. Therefore, we copy the
state of these variables after recognising the start of the first page
requested, and restore them (and the file position), before commencing the
true output pass. The Boolean |scanning| inhibits output to the \.{TYP} file
during this phase.
@<Globals...@>=
@!start_page_count:integer;
@!scanning:boolean;
@ @<Preset...@>=
scanning:=false;
@ Having found the first page which is eligible for translation to LN03
format, we skip rapidly through the remainder of the file, noting whether any
particular character of each font is actually used in the output. The file is
then further read until the postamble is reached, and the character usage
information used to generate the appropriate down-line loadable font(s) for
the LN03.
@^down-line loading of fonts@>
@<Gather font usage statistics@>=
begin
monitor('Gathering font usage statistics...');
@.Gathering font usage statistics@>
start_loc:=old_backpointer; { Remember where the first page started }
start_page_count:=page_count-1; { Preserve, since |bop|s will change this }
scanning:=true;@/
skip_pages; { Note usage of any required characters }
scanning:=false; {We've completed the scanning phase}
if signed_quad<>old_backpointer then
error('backpointer in byte ',cur_loc-4:1,
' should be ',old_backpointer:1,'!');
@:Error: backpointer in byte should be}{\quad\.{backpointer...should be...}@>
substitution_pass:=true; {Prevents spurious ``font already defined''s}
read_postamble;@/
@<Assign font/char mapping@>;@/
@<Download font files@>;@/
@<Restore pointers after font usage pass@>;@/
in_postamble:=false;
end
@ After we've finished the font usage pass, we must restore all the various
pointers into the |dvi_file| to their state preceding the pass, so that the
main program can just carry on with the actual setting of the glyphs.
@<Restore pointers after font usage pass@>=
old_backpointer:=start_loc; {for when the next |bop| is met}
move_to_byte(start_loc+1); {Go back to just after |bop| of starting page}
for k:=0 to 9 do count[k]:=signed_quad; {fill in page number array}
p:=signed_quad; {Swallow the |old_backpointer|}
page_count:=start_page_count
@*Map characters to LN03 fonts. At this point, we know which characters have
actually been invoked in each of the \TeX\ fonts. Since the LN03 has
limited font memory, as many characters as practicable are packed into a
188-character LN03 font; these characters will typically be taken from at
least two \TeX\ fonts.
@^LN03 laser printer@>
We therefore require to know, for each \TeX\ character, to which LN03 font it has
been allocated, and what character value it occupies in that font. When
characters are actually being typeset, only ten LN03 fonts are directly
accesible at any one time (corresponding to the Select Graphic Rendition
parameters 10..19); if the character to be typeset is allocated to a different
LN03 font, a suitable escape sequence will need to be output to change the
font which the LN03 is accessing.
The following declarations accomplish this mapping$\ldots$
@d left_first="!" {These are the ``printable'' character ranges}
@d left_last="~" {which may appear in an LN03 font}
@d right_first=left_first+128
@d right_last=left_last+128
@ For any particular \TeX\ font, |the_txf|, we count in
|font_occupancy[the_txf]| the number of characters invoked from that font.
To save all that indexing of arrays for this, the counting actually takes
place in |usage|. The array |txf_to_lnf| provides the number of the LN03
internal font into which |the_txf| has been mapped, whilst |txf_ord| is a
list of |the_txf|s in the order in which they were mapped (largest first).
For each of the LN03's internal fonts, |lnf|, the array element
|last_lnf[lnf]| contains the character code of the last character used
therein to map a character. |ch_widths| is an array which records the actual
width, in pixels, of each character created within an LN03 font.
The scalar variables used in this section include: (1) |txf_size| which
records how many characters are used in the next largest \TeX\ font
remaining; (2) |lnf_spare| records the number of unallocated glyphs
remaining in the LN03 font-file currently being filled; (3) |total_rasters|
counts the number of bytes of character rasters downloaded (this is reported
on the terminal and the \.{TYP} log file, and may be important to
installations without additional RAM font memory on the LN03); (4)
|pxl_ident| reminds us whether |the_txf| exists as a packed or unpacked
pixel file.
@<Globals in...@>=
@!font_occupancy:packed array [0..max_fonts] of 0..256; {Number of characters used}
@!usage:-1..256;
@!next_lnf:0..max_lnfonts+1; {Next LN03 font index to be allocated}
@!next_txf:0..max_fonts; {Next \TeX\ font to be mapped}
@!txf_size:-1..256; {Occupancy of largest remaining \TeX\ font}
@!txf_to_lnf:packed array [0..max_fonts] of -1..max_lnfonts; {Maps \TeX\ font to LN03 font}
@!txf_ord:packed array [0..max_fonts] of -1..max_fonts; {Contains \TeX\ font numbers in
order of mapping}
@!the_txf:0..max_fonts; {TeX font currently being mapped}
@!lnf_spare:integer; {``Spare'' room in current LN03 font}
@!last_lnf:packed array [0..max_lnfonts] of left_first..right_last;
{``Last'' character allocated in an LN03 font}
@!total_rasters:integer; {Total size of down-loaded fonts}
@!ch_widths:array [0..max_lnfonts,left_first..right_last] of integer;
@!pxl_ident:pixel_types;
@ At present, none of the LN03 fonts are `in use'; neither have any of them
been mapped to |SGR| (Select Graphic Rendition) codes.
We also clear out the variable in which we accumulate the count of all bytes
transferred as character rasters.
@<Set initial...@>=
for m:=0 to max_lnfonts do font_in_use[m]:=-1;
for m:=0 to max_SGR do who_uses[m]:=-1;
total_rasters:=0;
@ We now perform the actual mapping. Each \TeX\ font is considered in turn,
starting with the largest (most densely occupied) as yet unallocated. The
characters of this font are mapped to the next available LN03 font, and the
remaining space in the LN03 font is allocated to the largest \TeX\ fonts which
will fit. When no such \TeX\ font will fit in the remaining space, a new LN03
font is ``opened''.
Before we go ahead and map \TeX\ characters to physical ones, we have to
ensure that we're not still considering virtual fonts.
@<Assign font/char mapping@>=
@<Remap any virtual fonts@>;
@<Determine occupancy of each \TeX\ font@>;
next_lnf:=0; next_txf:=0; {Order of fonts}
repeat
lnf_spare:=left_last-left_first+1+right_last-right_first+1; {therefore =
188}
txf_size:=-1; {We're searching for largest font}
@<Find largest unallocated font@> ;
if txf_size>0 then {Assuming there are any unallocated}
begin
if next_lnf>max_lnfonts then capacity_exceeded('too many LN03 fonts');
@:capacity exceeded too many LN03 fonts}{\quad\.{too many LN03 fonts}@>
m:=left_first-1; {We'll start with the ``!'' character}
@<Map characters in \TeX\ font to allocated LN03 font@>;
@<Fit further \TeX\ fonts into LN03 font file@>;
incr(next_lnf)
end; {Fitted largest \TeX font, and some more, into an LN03 font}
until txf_size<=0; {Repeat until all fonts mapped}
@ We firstly require to know how many different characters have been invoked
from each of the \TeX\ fonts used in the document. The following section of
code performs this feat.
To speed things up, we avoid the double indexing of |glyphs| that would
occur if we used |glyph_map|; these definitions help.
@d first_glyph_index(#)==glyph_base[#]+font_bc[#]
@d last_glyph_index(#)==glyph_base[#]+font_ec[#]
@<Determine occupancy of each \TeX\ font@>=
for m:=0 to nf-1 do {Determine how occupied each \TeX\ font is}
begin usage:=0;
for n:=first_glyph_index(m) to last_glyph_index(m) do
if glyphs[n].loaded=wanted then incr(usage);
font_occupancy[m]:=usage;
txf_ord[m]:=-1; {Font unallocated}
txf_to_lnf[m]:=-1
end
@ Because we want to avoid having a single \TeX\ font overlapping two separate
fonts in the LN03 (which would require more control sequences to be output),
we prefer to have a large font starting off a new |ln_font|. This can be
achieved by finding the largest font that has not yet been allocated a
``home''.
(After mapping this font, we can use up the remaining unallocated characters of
the |ln_font| by putting smaller \TeX\ fonts into the same |ln_font|.)
It might prove embarrassing to attempt to map a virtual font, so we only
load fonts that are marked as |wanted|; this excludes |virtual| fonts, and
also those referenced from such a font from which all the glyphs are
|unused|.
@<Find largest unallocated font@>=
for n:=0 to nf-1 do
if (font_type[n]=wanted) and (font_occupancy[n]>txf_size) then
begin txf_size:=font_occupancy[n];
the_txf:=n
end
@ Having fitted a ``fullish'' \TeX\ font into a fresh LN03 font, we now
attempt to fill up the space remaining in the latter by squeezing in as many
smaller fonts as will fit. |lnf_spare| has been set to indicate how many
unallocated glyphs remain in the LN03 font, so we find the largest font whose
occupancy is not greater than this amount, and map it in; this process is
repeated until there is no \TeX\ font remaining that will fit in the
|lnf_spare| space remaining, at which point a new LN03 font is started (if
necessary).
@<Fit further \TeX\ fonts into LN03 font file@>=
while lnf_spare>0 do {Now find any \TeX\ font that fits in remainder}
begin usage:=-1; {Performs same function as |txf_size| above}
for n:=0 to nf-1 do {Find largest remaining that fits}
if (font_type[n]=wanted) and {provided it's physical, unmapped and}
(font_occupancy[n]>usage) and {it's more densely used}
(font_occupancy[n]<=lnf_spare) then {and it fits in space available}
begin usage:=font_occupancy[n];
the_txf:=n
end;@t\2@>
if usage > 0 then {There's one that fits!}
begin
m:=last_lnf[next_lnf]; {Last character allocated in LN03 font}
@<Map characters in \TeX\...@>;
end else
lnf_spare:=0; {Nothing else fits; force new LN03 font}
end { while spare space remains in LN03 font}
@ The following code maps the characters in the current (|the_txf|) \TeX\ font
to character positions in the current (|next_lnf|) LN03 font. No attempt is
made to make the mapped characters intelligible if the \.{LN3} file is
printed; this would be a ``nice'' revision of the program, but well nigh
impossible!
@<Map characters in \TeX\...@>=
txf_to_lnf[the_txf]:=next_lnf; {Map \TeX\ font to LN03 font}
txf_ord[next_txf]:=the_txf; {Save \TeX\ font numbers, largest first}
font_type[the_txf]:=yes; {Avoid considering this font again!}
incr(next_txf);
for k:=first_glyph_index(the_txf) to last_glyph_index(the_txf) do
with glyphs[k] do
if loaded=wanted then {Work through the characters of the \TeX\ font}
begin
@<Put LN03 code for this \TeX\ character into |m|@> ;
decr(lnf_spare); {Count one less free}
char_code:=m;
end;
last_lnf[next_lnf]:=m {Note ``last'' character allocated in LN03 font}
@ As we allocate characters from |the_txf| to the LN03 font |next_lnf|, we
have to ensure that the character codes used are those of \&{printing}
characters on the LN03. The LN03 uses the customary \.{C0} and \.{C1} control
code sets of the ISO/ASCII standards, and so the only printable characters are
`\.\SP' to `\.\TL' (|left_first..left_last|) and the corresponding
positions with the eighth bit set (|right_first..right_last|). This coding
scheme has ``holes'' in it, so we need to make appropriate adjustments.
Furthermore, if we permit a large font (with more than 188 characters used) to
span across into a second LN03 internal font, we have to make the transition
to the beginning of the second font, noting the false ``last character used''
and revising |lnf_spare| to reflect the new (unused) font.
@<Put LN03 code for this \TeX\ character into |m|@>=
if m=right_last then {Start another LN03 font for spanning \TeX\ font}
begin last_lnf[next_lnf]:=right_last;
lnf_spare:=left_last-left_first+1+right_last-right_first+1;
m:=left_first;
incr(next_lnf)
end
else
if m=left_last then m:=right_first {make transition to GR set in font}
else incr(m); {Next free character in LN03 font}
@*Generating LN03 font for downloading.
The following code generates the content of an LN03 font file, in memory,
and then calls |add_txf_to_lnf| to put the rasters into this file.
The file type extension is \.{.LN3}, and this file is always opened in the
user's default directory.
The next section of code actually generates the \.{LN3} file, starting with
its ``set up'' information. The program continues by creating and downloading
each necessary LN03 font, by successive calls of the function
|add_txf_to_lnf|, which returns as result the number of bytes downloaded in
the rasters of the characters contained therein; this is added to
|total_rasters| so that DVItoLN03 can report the size of all the fonts loaded.
The first (or only) LN03 font file downloaded is introduced by the |DCS|
(Device Control Sequence) |'0;1;0y'|; these three parameters indicate,
respectively, that the font file is in Digital font file format, is \&{not} to
print a summary sheet, and is to replace all previously downloaded fonts.
Successive LN03 fonts are separated from the previous ones by a simple |','|
character; whilst the end of the font download is marked by the |ST| (String
Terminator) character --- this is preceded by a textual comment.
@<Download font files@>=
@<Generate \.{LN3} file header@>;
total_rasters:=0;
if next_txf > 0 then {Unlikely that no characters were used, but...}
begin
write_ln(ln3_file,dcs,'0;1;0y'); {Replace all fonts with these}
for m:=0 to next_txf-1 do {Cycle through the ``used'' \TeX\ fonts}
begin k:=txf_ord[m]; {Get \TeX\ font number corresponding to next largest font}
name_pxl_file(k,file_name,pxl_ident);
if (m>0) then
@<Separate LN03 internal fonts@> ;
@<Copy glyphs to LN03 font; report size of rasters@>
end;
write_ln(ln3_file,';DVItoLN03 font load',st); {End of all fonts}
end;
print_ln('Total of ',total_rasters:1,' bytes of rasters downloaded');
write_ln(term_out,'Total of ',total_rasters:1,
' bytes of rasters downloaded',crlf);
@:Total of n bytes of rasters}{\.{Total of $n$ bytes of rasters...}@>
@<Complete mapping of virtual characters@>; {point to the real ones}
{Set page limits}
write(ln3_file,csi,x_min:1,';',page_wid:1,'s',csi,y_min:1,';',page_len:1,'r');
@<Designate first 10 fonts to SGRs |10..19|@>@;
@ We start by writing the LN03 directives to the front of the file. Since the
introduction of the LN03-Plus, this has been made more complicated, because
the sequence |esc, 'c'| is meaningful (and \&{not} as |RIS|!) when the
LN03-Plus is in the Tektronix emulation mode. Therefore, we start off by
outputting the sequence |esc, '[!p'| which is DEC's private ``soft terminal
reset'' (|DSCSTR|). (Note that this sequence uses the ``long-winded'' way of
specifying |csi|; this is because the LN03-plus ignores the eigth bit when it
is emulating a Tektronix terminal.) However, this won't necessarily reset an
`ordinary' LN03, so we output the ISO-standard |RIS| (Reset Initial State
$\equiv$ |esc,'c'|) sequence as well.
There then follow a number of control sequences which set up further
characteristics of the LN03: (1) |'?27h'| is a DEC private control sequence,
|DECPSP|, which selects proportional spacing mode, whereby the printer moves
forward by the recorded width of the character glyph rather than by some fixed
amount after printing; (2) |'11h'| \&{set}s the |PUM| (Positioning Unit Mode),
when set, this mode indicates that ``cursor movement'' sequences work in units
of either decipoints or pixels, which of these is determined by (3) |'7 I'|
which sets the |SSU| (Select Size Unit) to be in units of a single pixel; (4)
|'?52h'| is another DEC private sequence, \&{set}ting the |DECOPM| (Origin
Placement Mode) which places the origin for all measurement to the upper-left
corner of the physical page (the alternative is 0.25 inches from that point).
Finally, the sequence ending in |'t'| contains the |page_len| as a parameter,
and is another DEC private control sequence which sets the |DECLPP| (Lines per
Physical Page) to be the height of the printable area, in pixels.
@d DECSTR==esc+'[!p'
@d RIS==esc+'c'
@d DECPSP_on==csi+'?27h'
@d PUM_on==csi+'11h'
@d SSU_pixel==csi+'7 I'
@d DECOPM_on==csi+'?52h'
@<Generate \.{LN3} file header@>=
if orientation=landscape then
write_ln(ln3_file,DECSTR,RIS,csi,PFS_landscape)
else
write_ln(ln3_file,DECSTR,RIS,csi,PFS_portrait);
cur_orient:=orientation; new_orient:=orientation;
write_ln(ln3_file,DECPSP_on,PUM_on,SSU_pixel,DECOPM_on,csi,page_len:1,'t');
@<Select printing mode and feed tray on DEClaser printers@>
@ If we are starting the download of any except the first \TeX\ font, it may
be that the previous \TeX\ font was the last loaded into the current LN03
font, which means we will be starting a new LN03 font here. Therefore we need
to separate the two LN03 fonts from each other: the comma (`\.,') character is
used by the LN03 for this purpose.
@<Separate LN03 internal fonts@>=
if (txf_to_lnf[k]<>txf_to_lnf[txf_ord[m-1]]) then
write_ln(ln3_file,',')
@ The function |add_txf_to_lnf| performs all the work of creating an LN03 font
from the rasters in the font file. The result returned by this function
gives the total number of bytes created in the LN03 font for the given \TeX\
file.
@<Copy glyphs to LN03 font; report size of rasters@>=
total_rasters:=total_rasters+
add_txf_to_lnf(txf_to_lnf[k],k,file_name,pxl_ident)
@ We may have both packed and unpacked files available from which to take
pixel bitmaps, so we require some indication of what type of file is
actually available.
@<Types...@>=
@!pixel_types = (@!unavailable,@!packed_file,@!pixel_file);
@ The user has the option of using either pixel files (\.{.PXL}) and/or packed
pixel files (\.{.PK}) as the source of font raster information. There is the
further option of the directories in which these files are held being either
``flat'' (with all font files of one class in the same directory) or
``nested'' (where separate sub-directories are provided for each different
magnification).
File naming conventions used are illustrated by the following examples, which
show the names under which \.{cmr10} will be found at magsteps 0--2:
\yskip\centerline{\vbox{\offinterlineskip
\hrule
\halign{&\vrule#&\strut\hfil#\hfil&\quad\tt#\quad&\quad\tt#\quad&
\quad\tt#\quad&\quad\tt#\quad&\vrule#\cr
height2pt&\multispan5 &height2pt\cr
&\omit\strut Magnif-\hfil&\multispan2\hfil Packed Pixels\hfil&
\multispan2\hfil Unpacked Pixels\hfil&\cr
&\omit\strut\hfil ication&\hfil\rm Flat\hfil&\hfil\rm Nested\hfil&
\hfil\rm Flat\hfil&\hfil\rm Nested\hfil&\cr
height2pt&\multispan5 &height2pt\cr
\noalign{\hrule}
height2pt&\multispan5 &height2pt\cr
&0&CMR10.300PK&[300]CMR10.PK&CMR10.1500PXL&[1500]CMR10.PXL&\cr
&$1\over2$&CMR10.329PK&[329]CMR10.PK&CMR10.1643PXL&[1643]CMR10.PXL&\cr
&1&CMR10.360PK&[360]CMR10.PK&CMR10.1800PXL&[1800]CMR10.PXL&\cr
&2&CMR10.432PK&[432]CMR10.PK&CMR10.2160PXL&[2160]CMR10.PXL&\cr
height2pt&\multispan5 &height2pt\cr}
\hrule
}}
The program determines which class(es) of files are available from the
qualifiers \.{/PXL\_FONT\_DIRECTORY} (for the unpacked files) and
\.{/PK\_FONT\_DIRECTORY} (for the packed files). At least one of these
qualifiers must be present; if the qualifier provided is a VMS logical name,
it has been translated repeatedly until a VMS directory specification resulted.
Examination of (the first translation of) this specification then determines
whether the flat or nested structure is being used: if the directory
specification ends with the characters `\.{.]}' it indicates that the raster
files will be found in sub-directories, whilst absence of the `\..' indicates
a flat structure. The variables |pk_rooted| and |pxl_rooted| will already
have been set to reflect this.
Whether packed or unpacked pixel files are used, it is necessary to
compute a number which relates the magnification to either the sub-directory
name or the file extension. Since actual file names may well have been
computed using low-accuracy (possibly integer) arithmetic by operating system
commands, the possibility exists that the numbers may have been truncated to
the ``wrong'' integer compared with those that \.{DVItoLN03} computes;
therefore, we always arrange to look for the files using both the computed
number and those $\pm1$ from it.
The variable |dir_off| is used to compute the offset from the central figure,
which is held in |p|. The variables |file_stat| and |find_ctxt| are used in
conjunction with Vax/VMS system calls to determine whether the requested file
exists; |found_file| takes the values $-1$, indicating that the file wasn't
found, or $+1$ if it is found. It also can take the value $0$ whilst the
search is proceding, so a simple boolean cannot be used instead.
The following procedure converts the \TeX\ font referenced by |TeX_font| into
the correct strings for the directory and file name under which \.{DVItoLN03}
expects to find the pixel file.
@p procedure name_pxl_file ( @!TeX_font : integer;@/
var @!open_file_name: file_spec;
var @!what_it_was: pixel_types);
var @!p,@!dir_off,@!file_stat,@!find_ctxt,@!found_file : integer;@/
@!found_name,@!expected_file_spec : file_spec;
begin
found_file := -1;
expected_file_spec:='';@/
@<Locate packed pixel file@>;@/
if found_file<0 then
@<Locate unpacked pixel file@>;
if found_file<0 then
begin
warning('cannot find file ',expected_file_spec);
@:Warning: cannot find file}{\quad\.{cannot find file}@>
what_it_was:=unavailable
end
end;
@ We only look for packed pixel files if the \.{/PK\_FONT\_DIRECTORY}
qualifier was present, in which case its value will have been placed into the
string |tex_pk_font|. If the final characters of (the first logical name
translation of) this string were `\.{.]}', then a nested directory structure
is assumed (as indicated in |pk_rooted|), and the file is sought in the form
\hbox{\meta{disk}\meta{directory}\.[\meta{size}\.]\meta{name}\.{.PK}}.
Alternatively, if the directory is flat, the filename sought will be
\hbox{\meta{disk}\meta{directory}\meta{name}\..\meta{size}\.{PK}}.
Unfortunately, it is necessary to reconstruct the desired string for each
offset tried. The \meta{size} is effectively computed from 1.5
times the font design size compared with the \TeX ware default of 200
pixels/inch, yielding 300 for unmagnified fonts.
@d append_name==for n:=font_name[TeX_font] to font_name[TeX_font+1]-1 do
open_file_name:=open_file_name+xchr[names[n]]
@d flat_file_spec(#)==begin open_file_name := # ;
append_name;
open_file_name:=open_file_name+'.'+str_int(p+dir_off)
end
@d rooted_file_spec(#)==begin open_file_name:= # + '[' +
str_int(p+dir_off) + ']';
append_name;
open_file_name:=open_file_name+'.'
end
@<Locate packed pixel file@>==
if tex_pk_font.length > 0 then
begin
dir_off:=0;
p:=round(((resolution/1000)*mag*font_scaled_size[TeX_font])/font_design_size[TeX_font]);
repeat
if not pk_rooted then
flat_file_spec(tex_pk_font)
else
rooted_file_spec(tex_pk_font);
open_file_name:=open_file_name+'PK';
@<Attempt to find file with specification |open_file_name|@>
until found_file<>0;
if found_file>0 then
begin
open_file_name:=found_name;
what_it_was:=packed_file
end
end;
@ To determine the correct file of pixels to be read, the quantity |p| is
computed; since it is a \TeX ware convention that all pixel files are assumed
(at magnification=1000) to be at a |resolution| of 200 dots/inch, and the
LN03's |resolution| is \&{300} dots/inch, then an ordinary unmagnified font
will be sought with a
$\langle$\\{size}$\rangle$ of \.{1500}, whilst one magnified \.{\BS magstep 1}
will have a $\langle$\\{size}$\rangle$ of \.{1800}, \\{etc.} Computation of
|p| yields the appropriate value for the Vax/VMS directory or file type to be
used, and |VAX_lib_find_file| is used to verify that the file exists; to allow
for rounding errors, the directories |p|$\pm1$ are also sought.
@<Locate unpacked pixel file@>==
if tex_pxl_font.length > 0 then
begin
dir_off:=0;
p:=round(((resolution/200)*mag*font_scaled_size[TeX_font])/font_design_size[TeX_font]);
repeat
if not pxl_rooted then
flat_file_spec(tex_pxl_font)
else
rooted_file_spec(tex_pxl_font);
open_file_name:=open_file_name+'PXL';
@<Attempt to find file with specification |open_file_name|@>
until found_file<>0;
if found_file>0 then
begin
open_file_name:=found_name;
what_it_was:=pixel_file
end
end
@ Now that we've got a full file specification (barring the version number) in
|open_file_name|, we call upon the Vax/VMS system services to try to locate
the file. Before doing this, we make a note of the file specification if it's
that for the default file name (with |dir_off = 0|), and we haven't
previously noted a file name.
The find\_file system service requires a context variable (because it is
capable of finding a number of files matching a `wild-carded' file
specification), but we're not interested in that; therefore we always set
|find_ctxt| to zero before searching. \&{N.B.} This may not be written as the
constant |0| in the procedure call, because the system service writes a value
back to this variable.
The system service returns an odd status if the search is successful;
otherwise we change |dir_off| to a suitable new value, unless we've already
tried |p| and $p\pm1$, in which case we indicate the absence of the file by
setting |found_file = -1|.
@<Attempt to find file with specification |open_file_name|@>=
if (dir_off=0) and (expected_file_spec='') then
expected_file_spec:=open_file_name;
find_ctxt:=0;
file_stat:=VAX_lib_find_file(open_file_name,found_name,find_ctxt);
found_file:=0;
if odd(file_stat) then found_file:=1
else
begin
if dir_off=1 then found_file:=-1;
if dir_off=-1 then dir_off:=1 else dir_off:=-1
end
@ Although up to |max_lnfonts| may be downloaded by this program, the LN03 is
only capable of accessing any ten of these at any one time; it accomplishes
this by allocating the |SGR| (Select Graphic Rendition) designators |10..19|
to the first ten fonts downloaded. Fonts are designated by means of their
16-character internal font identifiers, allocated during the generation of
each font.
@<Designate first 10 fonts to SGRs |10..19|@>=
for m:=0 to VAX_min(max_SGR,next_lnf-1) do {Match first 10 fonts to |SGR| designators}
begin font_in_use[m]:=m; who_uses[m]:=m;
write_ln(ln3_file,dcs,'1;1',m:1,'}U00000',m:1,'002SK00GG',st)
end;
write_ln(ln3_file,csi,'10m'); {|SGR| 10 --- select most used font}
cur_out:=0 {We won't need to |SGR| if first output uses |SGR| 10}
@*Format of Virtual Fonts. The following description is cribbed almost
verbatim from Knuth's description of \.{VFtoVP}.
@^Knuth, D.~E.@>
@:VFtoVP program}{\.{VFtoVP} program@>
The idea behind \.{VF} files is that a general
interface mechanism is needed to switch between the myriad font
layouts provided by different suppliers of typesetting equipment.
Without such a mechanism, people must go to great lengths writing
inscrutable macros whenever they want to use typesetting conventions
based on one font layout in connection with actual fonts that have
another layout. This puts an extra burden on the typesetting system,
interfering with the other things it needs to do (like kerning,
hyphenation, and ligature formation).
These difficulties go away when we have a ``virtual font,''
i.e., a font that exists in a logical sense but not a physical sense.
A typesetting system like \TeX\ can do its job without knowing where the
actual characters come from; a device driver can then do its job by
letting a \.{VF} file tell what actual characters correspond to the
characters \TeX\ imagined were present. The actual characters
can be shifted and/or magnified and/or combined with other characters
from many different fonts. A virtual font can even make use of characters
from virtual fonts, including itself.
Virtual fonts also allow convenient character substitutions for proofreading
purposes, when fonts designed for one output device are unavailable on another.
@ A \.{VF} file is organized as a stream of 8-bit bytes, using conventions
borrowed from \.{DVI} and \.{PK} files. Thus, a device driver that knows
about \.{DVI} and \.{PK} format will already contain most of the mechanisms
necessary to process \.{VF} files.
A preamble appears at the beginning, followed by a sequence of character
definitions, followed by a postamble. More precisely, the first byte of
every \.{VF} file must be the first byte of the following ``preamble
command'':
\yskip\hang|pre| 247 |i[1]| |k[1]| |x[k]| |cs[4]| |ds[4]|.
Here |i| is the identification byte of \.{VF}, currently 202. The string
|x| is merely a comment, usually indicating the source of the \.{VF} file.
Parameters |cs| and |ds| are respectively the check sum and the design size
of the virtual font; they should match the first two words in the header of
the \.{TFM} file for the virtual font.
\yskip
After the |pre| command, the preamble continues with font definitions;
every font needed to specify ``actual'' characters in later
\\{set\_char} commands is defined here. The font definitions are
exactly the same in \.{VF} files as they are in \.{DVI} files, except
that the scaled size |s| is relative and the design size |d| is absolute:
\yskip\hang|fnt_def1| 243 |k[1]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|.
Define font |k|, where |0<=k<256|.
\yskip\hang|@!fnt_def2| 244 |k[2]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|.
Define font |k|, where |0<=k<65536|.
\yskip\hang|@!fnt_def3| 245 |k[3]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|.
Define font |k|, where |0<=k<@t$2^{24}$@>|.
\yskip\hang|@!fnt_def4| 246 |k[4]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|.
Define font |k|, where |@t$-2^{31}$@><=k<@t$2^{31}$@>|.
\yskip\noindent
These font numbers |k| are ``local''; they have no relation to font numbers
defined in the \.{DVI} file that uses this virtual font. The dimension~|s|,
which represents the scaled size of the local font being defined,
is a |fix_word| relative to the design size of the virtual font.
Thus if the local font is to be used at the same size
as the design size of the virtual font itself, |s| will be the
integer value $2^{20}$. The value of |s| must be positive and less than
$2^{24}$ (thus less than 16 when considered as a |fix_word|).
The dimension~|d| is a |fix_word| in units of printer's points; hence it
is identical to the design size found in the corresponding \.{TFM} file.
@d vf_id_byte=202
@<Glob...@>=
@!vf_file:byte_file;
@ The preamble is followed by zero or more character packets, where each
character packet begins with a byte that is $<243$. Character packets have
two formats, one long and one short:
\yskip\hang|long_char| 242 |pl[4]| |cc[4]| |tfm[4]| |dvi[pl]|. This long form
specifies a virtual character in the general case.
\yskip\hang|short_char0..short_char241|
|pl[1]| |cc[1]| |tfm[3]| |dvi[pl]|. This short form specifies a
virtual character in the common case
when |0<=pl<242| and |0<=cc<256| and $0\le|tfm|<2^{24}$.
\yskip\noindent
Here |pl| denotes the packet length following the |tfm| value; |cc| is
the character code; and |tfm| is the character width copied from the
\.{TFM} file for this virtual font. There should be at most one character
packet having any given |cc| code.
The |dvi| bytes are a sequence of complete \.{DVI} commands, properly
nested with respect to |push| and |pop|. All \.{DVI} operations are
permitted except |bop|, |eop|, and commands with opcodes |>=243|.
Font selection commands (|fnt_num0| through |fnt4|) must refer to fonts
defined in the preamble.
Dimensions that appear in the \.{DVI} instructions are analogous to
|fix_word| quantities; i.e., they are integer multiples of $2^{-20}$ times
the design size of the virtual font. For example, if the virtual font
has design size $10\,$pt, the \.{DVI} command to move down $5\,$pt
would be a \\{down} instruction with parameter $2^{19}$. The virtual font
itself might be used at a different size, say $12\,$pt; then that
\\{down} instruction would move down $6\,$pt instead. Each dimension
must be less than $2^{24}$ in absolute value.
Device drivers processing \.{VF} files treat the sequences of |dvi| bytes
as subroutines or macros, implicitly enclosing them with |push| and |pop|.
Each subroutine begins with |w=x=y=z=0|, and with current font~|f| the
number of the first-defined in the preamble (undefined if there's no
such font). After the |dvi| commands have been
performed, the |h| and~|v| position registers of \.{DVI} format and the
current font~|f| are restored to their former values;
then, if the subroutine has been invoked by a \\{set\_char} or \\{set}
command, |h|~is increased by the \.{TFM} width
(properly scaled)---just as if a simple character had been typeset.
(The following commands, already defined as macros with respect to the
\.{DVI} file, have the same interpretation when they appear in a virtual
font file: \hang |set_char_0|, |set1|, |set_rule|, |put1|, |put_rule|, |nop|,
|push|, |pop|, |right1|, |w0|, |w1|, |x0|, |x1|, |down1|, |y0|, |y1|, |z0|,
|z1|, |fnt_num_0|, |fnt1|, |xxx1|, |xxx4|, |fnt_def1|, |pre|, |post|.)
@d long_char=242 {\.{VF} command for general character packet}
@d improper_DVI_for_VF==139,140,243,244,245,246,247,248,249,250,251,252,
253,254,255
@ The character packets are followed by a trivial postamble, consisting of
one or more bytes all equal to |post| (248). The total number of bytes
in the file should be a multiple of~4.
@ Here are some functions, analogous to those for the |dvi_file|, for
reading one or more bytes from the |vf_file|.
Note that variable |flushing| is used to suppress the warning (when |eof| is
met on the |vf_file|) if bytes are merely being read to discard the
postamble.
@p procedure read_vf_file(var x:eight_bits);
label 9999;
begin while vf_count >= VAX_block_length do
begin get(vf_file,VAX_continue); vf_count:=vf_count-VAX_block_length;
incr(vf_block);
if eof(vf_file) then
begin if not flushing then
warning('VF file ended without postamble!');
@:Warning: VF file ended without postamble}{\quad\.{VF file ended without postamble}@>
x := post; goto 9999
end
end;
x := vf_file^[vf_count];
incr(vf_count); incr(vf_loc);
9999:
end;
@#
function @!vf_one: integer; {returns the next byte, unsigned}
var @!b: eight_bits;
begin
read_vf_file(b); vf_one:=b
end;
@#
function vf_byte:integer; {returns the next byte, signed}
var b:eight_bits;
begin read_vf_file(b);
if b<128 then vf_byte:=b @+ else vf_byte:=b-256;
end;
@#
function vf_two:integer; {returns the next two bytes, unsigned}
var a,@!b:eight_bits;
begin read_vf_file(a); read_vf_file(b);
vf_two:=a*256+b;
end;
@#
function vf_pair:integer; {returns the next two bytes, signed}
var a,@!b:eight_bits;
begin read_vf_file(a); read_vf_file(b);
if a<128 then vf_pair:=a*256+b
else vf_pair:=(a-256)*256+b;
end;
@#
function vf_three:integer; {returns the next three bytes, unsigned}
var a,@!b,@!c:eight_bits;
begin read_vf_file(a); read_vf_file(b); read_vf_file(c);
vf_three:=(a*256+b)*256+c;
end;
@#
function vf_trio:integer; {returns the next three bytes, signed}
var a,@!b,@!c:eight_bits;
begin read_vf_file(a); read_vf_file(b); read_vf_file(c);
if a<128 then vf_trio:=(a*256+b)*256+c
else vf_trio:=((a-256)*256+b)*256+c;
end;
@#
function vf_quad:integer; {returns the next four bytes, signed}
var a,@!b,@!c,@!d:eight_bits;
begin read_vf_file(a); read_vf_file(b); read_vf_file(c); read_vf_file(d);
if a<128 then vf_quad:=((a*256+b)*256+c)*256+d
else vf_quad:=(((a-256)*256+b)*256+c)*256+d;
end;
@ Here are the globals required to manipulate the VF file.
@<Glob...@>=
@!vf_count, @!vf_block, @!vf_loc : integer;
@!flushing : boolean;
@*Handling Virtual Fonts. Before we look around the fonts, we'd better see
if any of the fonts so far loaded are virtual ones; this can be determined
by whether a \.{VF} file exists for the given font.
We use a |while| loop, because the analysis of a virtual font most probably
will result in the addition of one or more new fonts, some of which may
themselves be virtual; to prevent infinite recursion with a self-referential
font, such additional fonts are only loaded if glyphs within the font are
activated.
@<Remap any virtual fonts@>=
TeX_font:=0; vf_ptr:=0;
while TeX_font < nf do {Currently defined fonts, and any this defines anew}
begin
if font_type[TeX_font]=wanted then
@<Locate virtual font metrics@>;
incr(TeX_font)
end;
@ This module determines the name of the \.{VF} file, if it exists,
corresponding to the current \TeX\ font.
@<Locate virtual font metrics@>=
if not no_virt_support then
begin
file_name:='';
for n:=font_name[TeX_font] to font_name[TeX_font+1]-1 do
if (names[n]>="a") and (names[n]<="z") then
file_name:=file_name+xchr[names[n]+"A"-"a"]
else
file_name:=file_name+xchr[names[n]];
open(vf_file,file_name,@=readonly@>,@=access_method@>:=@=direct@>,
@=user_action@>:=file_open,@=default@>:=tex_virtual+'.VF;0',
VAX_continue);
file_stat:=status(vf_file);
if (file_stat<>0) and (file_stat<>3) then
begin
case file_stat of
2: file_message := 'PAS-E-ERRDUROPE';
5: file_message := 'PAS-E-ACCMETINC';
6: file_message := 'PAS-E-RECLENINC';
7: file_message := 'PAS-E-RECTYPINC';
8: file_message := 'PAS-E-ORDSPEINC';
othercases
file_message := str_int(file_stat);
endcases;
abort('Couldn''t open virtual font ',file_name,'. Status=',file_message);
@:fatal error Couldn't open virtual font}{\quad\.{Couldn't open virtual font}@>
end
else if file_stat=0 then
begin
reset(vf_file);
vf_block:=0; vf_count:=0; vf_loc:=0;
font_type[TeX_font]:=virtual;
open_file_name:=def_file_name;@/
@<Find name and magnification of font file@>;@/
@<Report name of font file in use@>;@/
print_ln(' [virtual]');
write_ln(term_out,' is virtual',crlf);@/
read_VF;@/
close(vf_file,@=disposition@>:=@=save@>,VAX_continue);
end;
end
@ Globals for storing virtual font files: unlike \.{VFtoVP},
@.VFtoVP@>
we don't read the \\{entire} file into memory. We do, however, store those
|dvi| bytes that belong to some wanted character packets, but only for
glyphs which are more complex than simply imaging a character from some
other font; this is efficient, and also feasible, since VF files are never
very large (unlike physical font files!) Much of this code is, once again,
lifted from \.{VFtoVP}.
The array |vf| is global, along with the pointer into it, |vf_ptr|, because
we accumulate therein all setting sequences read from all virtual font files
processed. Most of the other variables are only required when actually
reading a particular virtual font, so are local to |read_VF|.
\yskip\hang |vf_take| When |dvi| bytes are to be ``read'' from array |vf|, the
variable |vf_take| will be non-negative and hence index an element of the
array; during normal input from the \.{DVI} file, a negative value ensures
that input is taken from the file itself.
\yskip\hang |TeX_font| will always point to the current virtual font being
processed, amongst all the fonts (both virtual and physical) that are in
the usual data structures. This global variable has the same name as
parameters of the procedures |name_pxl_file| and |add_txf_to_lnf|, in order
that common sections of code may be shared from the W{\mc EB}.
\yskip\hang |font_type| records whether a font is |virtual| or physical (in which
case the value |wanted| will have been set when the font's \.{TFM} file was
first read.
@<Glob...@>=
@!vf:packed array[0..vf_size] of eight_bits; {the \.{VF} input data goes here}
@!vf_ptr:0..vf_size; {first unused location in |vf|}
@!vf_take:-1..vf_size; {next byte to be ``read'' from |vf|}
@!TeX_font:0..max_fonts; {index into font metrics database for current virtual}
@!font_type: array [0..max_fonts] of download_status;
@ To ensure that we start by reading genuine |dvi| bytes from the \.{DVI}
file, we must make |vf_take| negative.
@<Preset...@>==
vf_take:=-1;
@ Here's the main procedure for reading virtual font files. It exists as a
procedure purely in order that local variable declarations can be made to
cater for those code sections which are also invoked within |define_font|.
When we encounter a font definition in the \.{VF} file, we read its
associated \.{TFM} file in the usual fashion; we associate a ``\TeX\ font
number'' with this font, determined from the highest font previously used
within the previous actual or virtual fonts, via the variable |font_base|.
@p procedure read_VF;
var @!f: 0..max_fonts; {Local index}
@!p: integer; {length of the area/directory spec}
@!n: integer; {length of the font name proper}
@!c,@!q,@!d: integer; {check sum, scaled size, design size}
@!r: 0..name_length; {index into |cur_name|}
@!j,@!k: 0..name_size; {indices into |names|}
@!mismatch: boolean; {names differ}
@!font_base: 0..max_fonts; {offset for local font numbers}
@!z,@!alpha,@!beta: integer; {to facilitate |fix_word| multiplications}
@!font_ref:array[0..max_fonts] of integer; {local font numbers}
@!font_ext:array[0..max_fonts] of integer; {``external'' font numbers}
@!font_ptr:0..max_fonts; {number of local fonts}
@!packet_start:array[0..255] of 0..vf_size; {character packet boundaries}
@!packet_found:boolean; {at least one packet has appeared}
@!temp_byte:eight_bits;@+@!temp_int:integer; {registers for simple calculations}
@!pl:integer; {packet length}
@<Declare |read_VF|'s local procedures@>@;
begin
font_base:=highest_font+1; flushing:=false;@/
@<Read the whole \.{VF} file@>;
end;
@ Again we cautiously verify that we've been given decent data.
@d vf_abort(#)==
abort('(VF file) ',#,crlf)
@!@:fatal error VF file ...}{\quad\.{(VF file)}@>
@<Read the whole \.{VF} file@>=
temp_byte:=vf_one;
if temp_byte<>pre then vf_abort('First byte isn''t `pre''');
@:fatal error VF file First byte}{\qquad\.{First byte isn't \it pre}@>
@<Read the preamble command@>;
@<Read and store the font definitions and character packets@>;
@<Read and verify the postamble@>
@ Here's where we read the preamble of the virtual font file. Note that we
make the assumption throughout that a `DVI unit' is \.{1sp} (as it always is
in output generated by \TeX), so that $\hbox{`DVI unit'}=16\hbox{\.{fix}}$.
@<Read the preamble command@>=
temp_byte:=vf_one;
if temp_byte<>vf_id_byte then vf_abort('Wrong version number in second byte!');
@:fatal error VF file Wrong VF version}{\qquad\.{Wrong VF version...}@>
temp_byte:=vf_one; {read the length of introductory comment}
for k:=1 to temp_byte do temp_int:=vf_one; {and discard it}
temp_int:=vf_quad;
if (temp_int<>0) and (font_check_sum[TeX_font]<>0) and
(temp_int<>font_check_sum[TeX_font]) then
error('VF checksum disagreed! TFM:',font_check_sum[TeX_font]:1,
' VF:',temp_int:1);
@:Error: VF checksum disagreed}{\quad\.{VF checksum disagreed}@>
z:=vf_quad div 16; {convert to |dvi| units}
if z<>font_design_size[TeX_font] then
error('VF design size disagreed! TFM:',font_design_size[TeX_font]:1,
' VF:',z:1);
@:Error: VF design size disagreed}{\quad\.{VF design size disagreed}@>
z:=font_scaled_size[TeX_font]; {but we're using it at this size}
@<Replace |z| by $|z|^\prime$ and compute $\alpha,\beta$@>;
@ The remainder of the file will consist of font definitions and character
packets; once any of the latter has been met, it is not legal for the former
to appear again.
@<Read and store the font definitions and character packets@>=
for k:=0 to 255 do packet_start[k]:=vf_size;
font_ptr:=0; packet_found:=false;
repeat temp_byte:=vf_one;
if temp_byte<>post then
if temp_byte>long_char then @<Read and store a font definition@>
else @<Read and store a character packet@>;
until temp_byte=post
@ The postamble of the virtual font file should consist purely of |post|
bytes; furthermore, the file should be a multiple of four bytes long.
@<Read and verify the postamble@>=
flushing:=true;
while (temp_byte=post)and not eof(vf_file) do temp_byte:=vf_one;
if not eof(vf_file) then
warning('extra junk at end of the VF file.');
@:Warning: extra junk at end of VF file}{\quad\.{extra junk...VF file}@>
if vf_loc mod 4 <> 0 then
warning('VF data not a multiple of 4 bytes')
@:Warning: VF data not a multiple of 4 bytes}{\quad\.{VF data not a multiple of 4 bytes}@>
@ The font definition reads as follows:
\yskip\hang|fnt_defN| 243..6 |k[1..4]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|.
\noindent where |k| is a local font number; this is used as an offset from
|font_base| when reading the associated \.{TFM} file.
@<Read and store a font definition@>=
begin if packet_found or(temp_byte>=pre) then
vf_abort('Illegal byte ',temp_byte:1,' at start of character packet!');
@:fatal error VF file Illegal byte}{\qquad\.{Illegal byte...at start...}@>
case temp_byte of
fnt_def1: font_ref[font_ptr]:=vf_one;
fnt_def1+1: font_ref[font_ptr]:=vf_two;
fnt_def1+2: font_ref[font_ptr]:=vf_three;
fnt_def1+3: font_ref[font_ptr]:=vf_quad {there are no other cases}
endcases;@/
font_ext[font_ptr]:=font_base+font_ptr; {invent ``external'' font number}
if font_ptr=max_fonts then vf_abort('References too many fonts!');
@:fatal error VF file References}{\qquad\.{References too many fonts}@>
@<Move the font parameters into position for font |nf|,
and print the font name@>;
print(' (local font ',font_ref[font_ptr]:1,' of font ',font_num[TeX_font]:1,')');
if highest_font<font_ext[font_ptr] then
highest_font:=font_ext[font_ptr];
@<Avoid reloading the font metrics...@>;
if f<nf then
begin font_ext[font_ptr] := font_num[f];
print_ln('---is identical to font ',font_num[f]:1);
incr(nf)
end else
@<Read the local font's \.{TFM} file and record the characters it contains@>;
incr(font_ptr);
end
@ We need to position some of this information in a similar fashion to when
\.{TFM} files are read during normal font definitions from the \.{DVI} file:
@<Move the font parameters into position for font |nf|,
and print the font name@>=
if nf = max_fonts then
capacity_exceeded('too many fonts (max fonts=',max_fonts:1,')');
@:capacity exceeded too many fonts}{\quad\.{too many fonts}@>
font_num[nf]:=font_ext[font_ptr]; {record invented ``external'' font number}
font_map[nf]:=nf; {until we know better, this font is new}
c:=vf_quad; font_check_sum[nf]:=c; {|c[4]|}
q:=vf_quad; font_scaled_size[nf]:=q; {|s[4]|}
if (q<0) or (q>@'77777777) then {|s| is negative or exceeds $2^{24}-1$}
vf_abort('Mapped font size is too big!');
@:fatal error VF file Mapped font size too big}{\qquad\.{Mapped font size too big}@>
d:=vf_quad div 16; font_design_size[nf]:=d; {|d[4]| in sp}
@<Determine actual scaling for physical font@>;
font_scaled_size[nf]:=q; {store revised scaling}
p:=vf_one; {|a[1]|}
n:=vf_one; {|l[1]|}
if font_name[nf]+n+p>name_size then
capacity_exceeded('too many names (name size=',name_size:1,')');
@:capacity exceeded too many names}{\quad\.{too many names}@>
font_name[nf+1]:=font_name[nf]+n+p;
print('Font ',font_num[nf]:1,': ');
if n+p=0 then print('null font name!')
@.null font name@>
else
for k:=font_name[nf] to font_name[nf+1]-1 do names[k]:=lower(vf_one);
incr(nf); print_font(nf-1); decr(nf); f:=nf;
@ When a |fnt_defN| is read from a virtual font file, the parameter |s| (in
variable |q|) represents the scaled size relative to the design size of the
virtual font. The latter was stored (in |font_design_size[TeX_font]|; scaled
by $2^{-4}$) when the original definition of the virtual font was read from
the \.{DVI} file, and has been checked during the reading of the virtual
font's preamble. Therefore, we will later call |in_TFM| to read the \.{TFM}
file for the physical font and adjust all dimensions by the appropriate
amount. To do this, we have to compute a new |q| by performing ``correct''
arithmetic on the expression $\hbox{|font_design_size[TeX_font]|}\times q$.
We utilize the same algorithm as is used for converting the width information
in a \.{TFM} file; local variable |z| has already been set to the virtual
font's design size, and converted to $|z|^\prime$, so we just need to split up
the value |s| into its constituent bytes and perform the multiplications.
By applying a cast to treat |q| as |unsigned|, we ensure that |b0<>0| for
negative values of |q|.
@d VAX_unsigned_type == unsigned
@<Determine actual scaling for physical font@>=
b0:=(q::VAX_unsigned_type div @'100000000)::eight_bits;
b1:=(q mod @'100000000) div @'200000;
b2:=(q mod @'200000) div @'400; b3:=q mod @'400;
q:=(((((b3*z) div @'400)+(b2*z)) div @'400)+(b1*z))div beta;
if b0>0 then q:=q-alpha; {assume $|q|<2^{25}$}
@ @<Read the local font's \.{TFM} file...@>=
begin
@<Move font name into the |cur_name| string@>;
open_tfm_file;
font_bc[nf]:=0; font_ec[nf]:=256; {will cause error if not modified soon}
if in_TFM(q) then @<Finish loading the new font info@>;
if font_bc[nf]<=font_ec[nf] then
if font_ec[nf]>255 then abort('---not loaded, bad TFM file!');
@:fatal error Font not loaded Bad TFM file}{\qquad\.{Bad TFM file}@>
print_ln('')
end
@ Here is a procedure for ``outputting'' a |dvi| sequence by storing it in
|vf|; each command is output in the minimum number of bytes.
We cheat and abuse VAX-\PASCAL's variant records to provide a simple
conversion between integers (signed or unsigned) and bytes in bigendian
order. Variable |byte_ct| determines how many bytes are actually required.
Parameter |code| is the command code being output; this will be incremented
(by |1..3|) if |2..4| parameter bytes are necessary. |param| is obviously
the parameter for |code|; |signed| is negative if |param| is to be
interpreted in twos-complement notation. Otherwise, |signed=0|, except for
|set_rule| and |put_rule| (which require that all four bytes are output for
the parameter) when |signed=1|. For output of the second parameter of these
latter two commands, |code=0|, which suppresses output of the |code| byte.
The following definition is used for any bytes which must be copied to array
|vf|.
@d vf_store(#)==begin vf[vf_ptr]:=#; incr(vf_ptr) end
@<Declare |read_VF|'s local procedures@>=
procedure vf_to_dvi(@!code:eight_bits;@!param:integer;@!signed:integer);
var @!byte_ct:0..3; {offset to be added to |code|}
@!cheat:packed record
case boolean of
false: (b0,b1,b2,b3:eight_bits);
true: (fullword:integer)
end;
begin
cheat.fullword:=param; byte_ct:=3; {assume needs 4-byte parameter}
if signed<=0 then
with cheat do
if ((b3=0) and ((signed=0) or (b2<128)))
or ((b3=255) and (signed<0) and (b2>=128)) then
begin
byte_ct:=2; {if |b3| superfluous, store 3-byte parameter}
if ((b2=0) and ((signed=0) or (b1<128)))
or ((b2=255) and (signed<0) and (b1>=128)) then
begin
byte_ct:=1; {if |b3| and |b2| superfluous, store two bytes}
if ((b1=0) and ((signed=0) or (b0<128)))
or ((b1=255) and (signed<0) and (b0>=128)) then
byte_ct:=0; {|b3|, |b2| and |b1| all superfluous; one byte parameter}
end
end;
if code<>0 then
vf_store(code+byte_ct); {store the appropriate command byte}
if byte_ct>2 then vf_store(cheat.b3);
if byte_ct>1 then vf_store(cheat.b2);
if byte_ct>0 then vf_store(cheat.b1);
vf_store(cheat.b0);
end;
@ This function computes how many bytes form the parameters of a command
taken from a character packet in the \.{VF} file; in the case of any special
sequences amongst these bytes (|xxx1..xxx4|), we read and discard all the
bytes here, since we don't support such sequences in virtual fonts. The
number of bytes thus discarded is returned, negated.
Any occurrence of a command which is illegal in a character packet causes
the font to be rejected.
@<Declare |read_VF|'s local procedures@>=
function vf_skip( @!o:eight_bits) : integer;
var @!temp : integer;
begin
case o of
sixty_four_cases(set_char_0),sixty_four_cases(set_char_0+64),
w0,x0,y0,push,pop,sixty_four_cases(fnt_num_0):
vf_skip:=0;
set1,put1,fnt1,right1,w1,x1,down1,y1,z1:
vf_skip:=1;
set1+1,put1+1,fnt1+1,right1+1,w1+1,x1+1,down1+1,y1+1,z1+1:
vf_skip:=2;
set1+2,put1+2,fnt1+2,right1+2,w1+2,x1+2,down1+2,y1+2,z1+2:
vf_skip:=3;
set1+3,set_rule,put1+3,put_rule,right1+3,w1+3,x1+3,down1+3,
y1+3,z1+3,fnt1+3:
vf_skip:=4;
xxx1: begin temp:=vf_one; vf_skip:=-1-temp @+end;
xxx1+1: begin temp:=vf_two; vf_skip:=-2-temp @+end;
xxx1+2: begin temp:=vf_three; vf_skip:=-3-temp @+end;
xxx1+3: begin temp:=vf_quad; vf_skip:=-4-temp @+end;
improper_DVI_for_VF:
vf_abort('contains illegal command in character packet');
@:fatal error VF file contains illegal command}{\qquad\.{contains illegal command...}@>
endcases;
if (o>=xxx1) and (o-xxx1<4) then
begin
decr(vf_ptr); {``unstore'' the |xxx| command byte}
while temp>0 do
begin o:=vf_one; decr(temp) end {discard |xxx| sequence bytes}
end
end;
@ This procedure adds to the saved packet a command to select a font; this
will be an pseudo external font number, converted from the local font number
used when a physical font is mapped into the virtual one. The procedure has
the side-effect of setting its second parameter to index the stored metrics
of the newly-selected font.
@<Declare |read_VF|'s local procedures@>=
procedure store_fnt(@!num:integer; var @!f:integer);
var@!k:0..max_fonts;
begin
f:=-1; {this value allows us to detect lack of the font}
for k:=0 to font_ptr-1 do {scan the local font reference numbers}
if font_ref[k]=num then f:=k; {find its entry}
if f<0 then
vf_abort('packet ',c:1,'invokes unmapped font ',num:1);
@:fatal error VF file packet invokes}{\qquad\.{packet invokes unmapped font}@>
num:=font_ext[f]; {convert to pseudo external number}
f:=0; while font_num[f]<>num do incr(f);
if num < 64 then vf_store(fnt_num_0+num) else
vf_to_dvi(fnt1,num,0); {|fntN k[N]|}
end;
@ This procedure adds to the saved packet a command to set a character; it
is required because we have to read all the bytes of such a command (in
order to determine to what character a packet is being mapped).
@<Declare |read_VF|'s local procedures@>=
procedure store_char(@!cc:integer);
begin
if cc < 128 then vf_store(set_char_0+cc) else
vf_to_dvi(set1,cc,0); {|setN c[N]|}
end;
@ When a character packet is being read from the virtual font file, we utilize
this procedure to store the bytes, translating any font selection commands to
reference the pseudo external fonts that have been created corresponding to
each local font of the virtual font file.
The packet taken from the \.{VF} file is surrounded by a |push|/|pop| pair
to maintain the program's conception of the current position. We start by
assuming local font $0$, which is that loaded as pseudo external font
|font_base|.
After the packet has been copied, an |eop| is inserted into the stored
packet to permit detection of the end when the packet is later ``played
back''.
@<Declare |read_VF|'s local procedures@>=
procedure vf_copy(@!pl,@!cc : integer);
var @!f:-1..max_fonts; {index into physical font data structures}
@!param_len:integer; {count of parameter bytes}
@!local_font:integer; {index into local font data structures}
@!mapped_char:integer; {index into a locally referenced font}
@!command:eight_bits; {next |dvi| command in \.{VF} file}
@!simple_packet, {packet contains only one |setN|, with maybe one |fntN|}
@!one_set,@!one_fnt:boolean; {|false| if more than one of each}
@!start_ptr,seq_ptr:0..vf_size; {pointers into bytes stored in |vf|}
@!scaling_required:boolean; {dimension needs revision before storing}
begin
if vf_ptr+pl>=vf_size then capacity_exceeded('too many virtual characters');
@:capacity exceeded too many virt}{\quad\.{too many virtual characters}@>
start_ptr:=vf_ptr; vf_store(push);
store_fnt(0,f);
glyph_map(TeX_font)(cc).font_code:=f;
one_set:=false; one_fnt:=false; simple_packet:=true; seq_ptr:=vf_ptr;
while pl>0 do
begin
command:=vf_one;
vf_store(command);
decr(pl);
param_len:=vf_skip(command);
@<Act upon any font changing command@>;
@<Act upon any character setting command@>;
@<Act upon commands that require scaling of parameters@>@;
case command of
sixty_four_cases(set_char_0),sixty_four_cases(set_char_0+64),
four_cases(set1): one_set:=true;
sixty_four_cases(fnt_num_0),four_cases(fnt1): one_fnt:=true;
set_rule, put_rule: {these have \\{two} 4-byte parameters}
begin param_len:=param_len-4; simple_packet:=false
end;
othercases
simple_packet:=false;
endcases;
if param_len<0 then {|abs(param_len)| bytes already copied}
pl:=pl+param_len
else
while param_len>0 do {copy remaining bytes of |command|}
begin command:=vf_one; vf_store(command);
decr(pl); decr(param_len)
end
end;
vf_store(pop);
vf_store(eop);
if simple_packet then vf_ptr:=start_ptr {discard the packet}
else with glyph_map(TeX_font)(cc) do
begin loaded:=virtual; {indicate where \AM\ how to find it}
seq_off:=start_ptr
end
end;
@ If a character packet contains any command to select a font, the command
must be expanded, and the local font number converted into a reference to
the appropriate pseudo external font. Since this may have the effect of
reading some bytes from the packet, we reduce our goal by the appropriate
quantity.
If this is the first font changing command in the packet, the internal font
it defines is placed into the current character's |glyph_map| entry, to
permit later overwriting with the entry for the physical character. If,
moreover, it is the first command \\{stored} in the packet, apart from the
initial |push| and the invocation of local font $0$, we can over-write the
latter.
@<Act upon any font changing command@>=
case command of
sixty_four_cases(fnt_num_0): local_font:=command-fnt_num_0;
fnt1: local_font:=vf_one;
fnt1+1: local_font:=vf_two;
fnt1+2: local_font:=vf_three;
fnt1+3: local_font:=vf_quad;
othercases
do_nothing
endcases;
if (command>=fnt_num_0) and (command<=fnt1+3) then
begin decr(vf_ptr); {``unstore'' the command byte}
store_fnt(local_font,f);
param_len := -param_len; {indicate bytes already copied}
if vf_ptr=seq_ptr then {we can overwrite the `|fnt_num_0|'}
vf_ptr:=start_ptr+1; {by writing immediately after the |push|}
if not one_fnt then glyph_map(TeX_font)(cc).font_code:=f
else simple_packet:=false
end;
@ Character packets usually contain one or more commands to set a character;
this code reads all necessary bytes to determine the code for the character
being set. Note that this code is \&{not} activated for commands to \\{put}
characters, since these usually appear only in more complex character
packets. If no previous character setting command has been met, then the
character code is written into the |glyph_map|.
@<Act upon any character setting command@>=
case command of
sixty_four_cases(set_char_0),sixty_four_cases(set_char_0+64):
mapped_char:=command-set_char_0;
set1: mapped_char:=vf_one;
set1+1: mapped_char:=vf_two;
set1+2: mapped_char:=vf_three;
set1+3: mapped_char:=vf_quad;
othercases
do_nothing
endcases;
if (command>=set_char_0) and (command<=set1+3) then
begin decr(vf_ptr); {``unstore'' the command byte previously copied}
store_char(mapped_char); {store a new character setting sequence}
param_len:=-param_len; {indicate this many bytes already consumed}
with glyph_map(f)(mapped_char) do
if loaded=unused then loaded:=wanted;
if not one_set then glyph_map(TeX_font)(cc).char_code:=mapped_char
else simple_packet:=false;
if font_type[f]=unused then font_type[f]:=wanted;
end;
@ Many other |dvi| commands within a character packet require that the
parameters be read, because they must be scaled by the size at which the
virtual font is used. We start by assuming that the |dvi| bytes \&{will} be
read from the \.{VF} file, and thus negate |param_len| so that the main loop
will know that those bytes have been read. If we \&{don't} read the
command's parameters, |param_len| will be negated back again, and thus lead
to that number of bytes being copied verbatim from the \.{VF} file to |vf|.
@<Act upon commands that require scaling of parameters@>==
param_len:=-param_len;
scaling_required:=false;
case command of
set_rule,put_rule:
begin q:=vf_quad; {read height of rule}
decr(vf_ptr); {``unstore'' command byte}
@<Determine actual scaling for physical font@>;
vf_to_dvi(command-3,q,1); {store command and first parameter}
q:=vf_quad; {read width of rule}
param_len:=-8; {we've consumed 8 bytes from |pl|}
@<Determine actual scaling for physical font@>;
vf_to_dvi(0,q,1) {store second parameter}
end;
right1,w1,x1,down1,y1,z1:
begin q:=vf_byte; scaling_required:=true end;
right1+1,w1+1,x1+1,down1+1,y1+1,z1+1:
begin q:=vf_pair; scaling_required:=true end;
right1+2,w1+2,x1+2,down1+2,y1+2,z1+2:
begin q:=vf_trio; scaling_required:=true end;
right1+3,w1+3,x1+3,down1+3,y1+3,z1+3:
begin q:=vf_quad; scaling_required:=true end;
othercases
param_len:=-param_len
endcases;
if scaling_required then
begin
decr(vf_ptr); {``unstore'' the command byte}
@<Determine actual scaling for physical font@>;
vf_to_dvi(command+param_len+1,q,-1) {store command again, with revised dimension}
end;
@ When a character packet is found, we only bother with it if it defines a
character that's used in the document being processed; other character
packets are merely skipped.
If the packet consists merely of commands to set a single character from one
of the physical fonts to which the virtual font is mapped, then we can
simply alter the information in the |glyph_map| to redirect requests to the
appropriate character of the physical font; the |glyph_map| of that font is
in turn marked to indicate that the character is used in the current
document.
On the other hand, if the character packet requires more complex positioning
commands, the whole packet is stored in array |vf|; the starting position of
the sequence is recorded in field |seq_off| of the |glyph_map| entry, whilst
the |loaded| field is given the value |virtual|. Field |font_code| will
contain the internal font number assigned to local font zero of the virtual
font, whereas the font selection commands stored in |vf| use the pseudo
external font numbers, since these bytes will require interpretation by the
normal dvi-reading routines.
@<Other values of |download_status|@>==,@!virtual
@ @<Other fields of |glyph_info|@>==@!seq_off : integer;
@ The \.{VF} format supports arbitrary 4-byte character codes, but \.{VPL}
format presently does not.
Since the |tfm| widths in the character packets are unscaled, it would be
difficult to ensure that they correspond with those determined when the
virtual font's metrics were read, so they will be ignored at this time.
@d nonexistent(#)==((#<font_bc[TeX_font])or(#>font_ec[TeX_font])or
(char_width(TeX_font)(#)=invalid_width))
@<Read and store a character packet@>=
begin if temp_byte=long_char then
begin pl:=vf_quad; c:=vf_quad; temp_int:=vf_quad;
{|pl[4]| |cc[4]| |tfm[4]|}
end
else begin pl:=temp_byte; c:=vf_one; temp_int:=vf_trio;
{|pl[1]| |cc[1]| |tfm[3]|}
end;
if nonexistent(c) then vf_abort('Character ',c:1,' does not exist!');
@:fatal error VF file Character c does not exist}{\qquad\.{Character does not exist}@>
if packet_start[c]<vf_size then
warning('Discarding earlier packet for character ',c:1);
@:Warning: Discarding earlier packet}{\quad\.{Discarding earlier packet...}@>
if pl<0 then vf_abort('Negative packet length!');
@:fatal error VF file Negative packet length}{\qquad\.{Negative packet length}@>
packet_start[c]:=vf_ptr;
if glyph_map(TeX_font)(c).loaded <> unused then
vf_copy(pl,c)
else
while pl>0 do {discard unwanted character packet}
begin temp_byte:=vf_one;
decr(pl)
end;
packet_found:=true;
end
@ Once all characters from physical fonts have been downloaded (and hence
may be assumed to have been mapped to character positions in the LN03's
downloaded fonts), we can complete the |glyph_map| entries for any virtual
fonts that may be present; for simple characters, this just involves copying
the entry for the appropriate character in the physical font.
We start with the most recently loaded font, so that chains through a number
of virtual fonts will be completed correctly.
@<Complete mapping of virtual characters@>=
for TeX_font:=nf-1 downto 0 do
if font_type[TeX_font]=virtual then {only process virtual fonts}
begin
for m:=first_glyph_index(TeX_font) to last_glyph_index(TeX_font) do
{cover all possible characters}
with glyphs[m] do
if loaded=wanted then
glyphs[m]:=glyph_map(font_code)(char_code)
else if loaded=virtual then {first char in sequence from this \TeX\ font}
if font_type[font_code]=wanted then {provided it's a physical font}
font_code:=txf_to_lnf[font_code] {maps to this LN03 font}
end;
@*Description of LN03 Font Files. The font files downloaded conform to DEC
standard 180; the definitions given below have been derived by the author from
the description of this standard included in the ``Font File Format Manual'',
Version 1, Variant 0, available on request from DEC.
Although we refer herein to font `files' they actually are always considered
to reside in memory, either that available in the basic printer, or extra
memory provided by plug-in RAM cartridges; presumably the same format is also
used to encode the built-in fonts, and those available in plug-in ROM
cartridges.
A font file is divided into ten regions; all data items may be referenced
\\{via} their offsets within one of these regions, which are predeterminable
for any particular combination of characters included in the file. All
multi-byte quantities in a font file are stored in Vax/VMS standard form, with
the least-significant byte being at the \&{lower} address; this is the
opposite to the normal `Bigendian' order of most \TeX ware.
Since it is necessary to build the entire font file in memory before it can be
included in the \.{LN3} file for downloading to
the printer, we allocate a large array of memory sufficiently large to store
the rasters for the largest imaginable font file; the definition of
|lnf_bufsize| probably exceeds the maximum amount of memory available on the
printer by a comfortable margin! This program refers to this `file' in memory
as the \.{LNF} file, although it is never written to a file as such; if this
program is adapted for use on a computer without virtual memory, it might
@^system dependencies@>
be necessary to utilize a random-access file to build the |lnf_file|.
Since the partially-built LN03 font file must be retained between successive
calls of |add_txf_to_lnf|, its data structures need to be declared globally.
Information in this ``file'' can consist of single bytes, unsigned (two-byte)
words, and signed (four-byte) longwords, as well as four-character strings, so
we define an |lnf_block| as a \PASCAL\ variant record so that this may be
(ab)used to permit the different methods of access.
We also declare here the |pxl_dir_type| which describes the 16-byte directory
record in a \.{PXL} file for any character's glyph.
@<Types...@>=
type @!lnf_block=packed record
case integer of
0: (@!bytes:packed array[0..511] of eight_bits);
1: (@!words:packed array[0..255] of sixteen_bits);
2: (@!longs:packed array [0..127] of integer);
3: (@!str:packed array [0..127] of packed array [1..4] of char)
end;
@ Many variables used by |add_txf_to_lnf| are declared globally, so that the
information contained therein is preserved between successive calls of the
function. |lnf| is the large memory-held `file' referred to above.
The variables |psize|, |lsize| and |msize| accumulate counts of raster bytes
generated in portrait, landscape and mixed modes (this program only creates
portrait mode rasters). |ras_beg| and |ras_len| are used to form the
descriptor pointing to the Character Definition Region, whilst
|lnf_pool_offset| points to the string pool. |lnf_len| keeps track of the
total size of the `file', whilst |using_GR| is set true when the |GR|
(right-hand half) of the font is being generated. (The LN03 supports use of
the |GL| and |GR| characters sets, corresponding to characters in the
(hexadecimal) range |@"21..@"7E| and |@"A1..@"FE|, respectively.)
The variable |file_stat| receives the exit status from VAX-Pascal's file
handling routines.
@<Globals...@>=
@!lnf: array[0..lnf_bufsize] of lnf_block;
@!psize,@!lsize,@!msize: integer;
@!ras_beg,@!ras_len: integer;
@!lnf_pool_offset,@!lnf_len: integer;
@!using_GR: boolean;
@!file_stat:integer;
@ The first region of an |lnf_file| is the file header. This contains, as its
first longword (32-bit) quantity, a count of the total size of the file, in
bytes. This is followed by the string |'FONT'|, to confirm that this is a DEC
Std 180 font. This is followed immediately by a ``string descriptor''
pointing to the font-file identifier. Font files contain many such descriptors;
each consists of two longwords, the first contains the string's length (in its
lower half), whilst the latter is the offset (relative to the start of the
file) of the first character of the string.
The font-file identifier descriptor is followed immediately (which is unusual)
by the characters of the identifier itself; for details of the content of this
string, read DEC Std 180. The date and time of creation of the font follows;
this program arbitrarily sets these to 14:00:00 on 11-SEP-1973.
There then follow a number of descriptors pointing to the remaining regions of
the font file; these are, in order: |fnt_attr| font attributes region;
|fnt_params| font parameters region; |fnt_char_directory| font character
directory region; |fnt_seg_list| font segment list region; |fnt_future| font
future information region; |fnt_strings| font string pool region; |fnt_kerns|
font kerning information region; |fnt_chars| font character defintions region.
The header finishes with the character count information field, which contains
five longwords and three character locators (|fnt_first|$\to$|fnt_space|), the
organization flags, a count of the number of parameters used in each raster
definition and the raster expansion information field, which records the
number of characters defined in this file, and the number of bytes of rasters
required to define them; this field occupies a further 12 longwords
(|font_in_file_count|$\to$|fnt_mix_comp|).
@d fnt_total_size=0
@d fnt_identifier=fnt_total_size+4
@d fnt_version=fnt_identifier+4
@d fnt_file_id=@"14 {Where font identification appears}
@d fnt_date=@"58 {Where creation date starts}
@d fnt_attr=fnt_date+12 {Font attributes descriptor}
@d fnt_params=fnt_attr+8 {Font parameters descriptor}
@d fnt_char_directory=fnt_params+8 {Character directory descriptor}
@d fnt_seg_list=fnt_char_directory+8 {Font segment list descriptor}
@d fnt_future=fnt_seg_list+8 {Future info descriptor}
@d fnt_strings=fnt_future+8 {String pool descriptor}
@d fnt_kerns=fnt_strings+8 {Font kerning infor descriptor}
@d fnt_chars=fnt_kerns+8 {Character definitions descriptor}
@d fnt_first=fnt_chars+8 {First character in font}
@d fnt_last=fnt_first+4 {Last character in font}
@d fnt_less_than=fnt_last+4 {Locator for characters less then first}
@d fnt_greater_than=fnt_less_than+4 {Locator for characters greater than last}
@d fnt_error=fnt_greater_than+4 {Locator for the error character}
@d fnt_extension=fnt_error+4 {Extension count and use}
@d fnt_space=fnt_extension+8 {Space character code}
@d fnt_organisation=fnt_space+4 {Organisation flags}
@d fnt_no_of_params=fnt_organisation+4 {Number of character parameters}
@d fnt_in_file_count=fnt_no_of_params+4 {Number of locators in this file}
@d fnt_null_count=fnt_in_file_count+4 {Null locator count}
@d fnt_char_count=fnt_null_count+4 {Number of character definitions}
@d fnt_alt_blocks=fnt_char_count+4 {Number of alternate character blocks}
@d fnt_rasters=fnt_alt_blocks+4 {Number of rasters}
@d fnt_compressed=fnt_rasters+4 {Number of compressed rasters}
@d fnt_portrait=fnt_compressed+4 {Portrait mode byte count}
@d fnt_landscape=fnt_portrait+4 {Landscape byte count}
@d fnt_mixed=fnt_landscape+4 {``Mixed'' byte count}
@d fnt_port_comp=fnt_mixed+4 {Portrait compressed byte count}
@d fnt_land_comp=fnt_port_comp+4 {Landscape compressed byte count}
@d fnt_mix_comp=fnt_land_comp+4 {``Mixed'' compressed byte count}
@ The font attributes region always follows the header; items within this
region are referenced through their local offsets from the start of the
region.
The |fnt_attr_flags| carries 32 bits of font attribute information; these are
set arbitrarily by this program --- see the coding for details.
The character set designator (within the string pool) uniquely identifies the
character set contained within this font, and is accessed by the descriptor
|fnt_char_set|. Similarly the |fnt_type_ID|, |fnt_type_family|, |fnt_font_ID|,
|fnt_type_cat| and |fnt_descr| descriptors access further strings in the
string pool.
Further fields in the font attributes region define numerical attributes of
the font (|fnt_type_size| thru |fnt_dev_char|). The attributes region
concludes with a further two descriptors, pointing to strings giving the names
of the foundry and designer responsible for the font.
Numerical quantities referring to the dimensions of characters in DEC LN03
font files are commonly in ``points'' or ``centipoints'' (the latter obviously
being $1\over{100}$th of the former). However, these are not true printer's
points (of which there are 72.27 to an inch); rather they are that horrible
compromise which imagines that there are exactly 72 points to an inch (and
which \TeX\ refers to as a \.{bp}). Centipoints are also referred to as
`Gutenbergs', in tribute to the inventor of movable type. It is quite
convenient for the LN03 (with 300 dots per inch) to work internally in
Gutenbergs, of which there are 7200 to an inch, since 24 Gutenbergs therefore
translate into a single pixel.
@d fnt_attributes=fnt_mix_comp+4 {Start of attributes region}@#
{Following define offsets within the Font Attributes Region of the ``file''}
@d fnt_attr_flags=0 {Flags e.g. roman}
@d fnt_char_set=fnt_attr_flags+4 {DESCR for character set designator}
@d fnt_designator_len=7 {Number of characters in set desginator string}
@d fnt_type_ID=fnt_char_set+8 {DESCR for Font Type Family ID}
@d fnt_type_ID_len=7 {Number of chars in Font Family ID string}
@d fnt_type_family=fnt_type_ID+8 {DESCR for Type Family Name}
@d fnt_type_family_len=16 {Number of chars in Type Family String}
@d fnt_font_ID=fnt_type_family+8 {DESCR for Font ID string}
@d fnt_font_ID_len=16 {Number of chars in Font ID string}
@d fnt_string_pool_len=fnt_designator_len+fnt_type_ID_len+fnt_type_family_len+fnt_font_ID_len
@d fnt_type_cat=fnt_font_ID+8 {DESCR for Type Category}
@d fnt_descr=fnt_type_cat+8 {DESCR for Font Description}
@d fnt_type_size=fnt_descr+8 {Font type size, in LN03 points}
@d fnt_average_width=fnt_type_size+4 {in centipoints}
@d fnt_resolution=fnt_average_width+4 {in $2^{-16}$ centipoints per pixel}
@d fnt_weight=fnt_resolution+4 {Coded 0..35. Normal is 16}
@d fnt_horiz_prop=fnt_weight+4 {0..35. Unexpanded/uncompressed is 16}
@d fnt_horiz_ratio=fnt_horiz_prop+4 {Ratio to a "normal" font}
@d fnt_pixel_aspect=fnt_horiz_ratio+4 {Ratio of X:Y of device's pixels}
@d fnt_rotation=fnt_pixel_aspect+4 {Ratio giving tangent of rotation}
@d fnt_dev_char=fnt_rotation+4 {Device specific characteristics}
@d fnt_foundry=fnt_dev_char+4 {DESCR of name of foundry}
@d fnt_designer=fnt_foundry+8 {DESCR of name of font designer}@#
@d fnt_attr_len=fnt_designer+8 {Length of Font Attributes Region of file}
@ The next region of the font file is the font parameters region. All fields
of this region are referenced by means of their offsets within the region.
The first field is a 32-bit flags field, of which only two bits have defined
meanings.
The next field contains 8 items
(|fnt_underline_offset|$\to$|fnt_shadow_offset|) which define the position for
electronic over-, through- and under-lining, and the slant of the characters
and the delta offsets for emboldening. None of these features are utilized by
DVItoLN03.
The next field (|fnt_superscript|$\to$|fnt_subscript|) describe the horizontal
and vertical offsets within the characters for the placement of super- and
sub-scripts. The following field (|fnt_centreline|$\to$|fnt_digit_width|)
define various horizontal spacing parameters, whilst the final field of the
font parameters region (|fnt_topline_offset| thru |fnt_white_below|) define
vertical spacing parameters.
@d fnt_parameters=fnt_attributes+fnt_attr_len {Start of Parameters Region}@#
{Following define offsets within the Font Parameters Region of the ``file''}
@d fnt_param_flags=0
@d fnt_underline_offset=fnt_param_flags+4 {All following in LN03 centipoints}
@d fnt_underline_thickness=fnt_underline_offset+4
@d fnt_thru_offset=fnt_underline_thickness+4 {Where strike-through line goes}
@d fnt_thru_thickness=fnt_thru_offset+4
@d fnt_overline_offset=fnt_thru_thickness+4
@d fnt_overline_thickness=fnt_overline_offset+4
@d fnt_slant=fnt_overline_thickness+4 {Ratio giving tan of slope from vertical}
@d fnt_shadow_offset=fnt_slant+4 {$\delta x$ and $\delta y$}
@d fnt_superscript=fnt_shadow_offset+4 {|y| and |x| offsets}
@d fnt_subscript=fnt_superscript+8
@d fnt_centreline=fnt_subscript+8 {Distance from left to centre widest char}
@d fnt_min_space=fnt_centreline+4 {Minimum acceptable width of interword space}
@d fnt_max_space=fnt_min_space+4
@d fnt_mean_space=fnt_max_space+4 {Width of space for monospace fonts}
@d fnt_em_width=fnt_mean_space+4 {Width of an |em| space}
@d fnt_en_width=fnt_em_width+4 {Width of an |en| space}
@d fnt_thin_space=fnt_en_width+4
@d fnt_digit_width=fnt_thin_space+4 {All digits usually same width}
@d fnt_topline_offset=fnt_digit_width+4 {From baseline to top of type size}
@d fnt_accent=fnt_topline_offset+4 {Where floating accents go}
@d fnt_halfline_offset=fnt_accent+4 {Where centre bar of an \.H appears}
@d fnt_overall_height=fnt_halfline_offset+4 {Sum of |height+depth|}
@d fnt_height=fnt_overall_height+4 {Includes ``leading'' above}
@d fnt_depth=fnt_height+4
@d fnt_H_height=fnt_depth+4
@d fnt_ex_height=fnt_H_height+4
@d fnt_white_above=fnt_ex_height+4
@d fnt_white_below=fnt_white_above+4@#
@d fnt_params_len=fnt_white_below+4 {Length of Font Parameters Region}
@ The next major region contains the character directory.
@d fnt_char_dir=fnt_parameters+fnt_params_len {Where directory goes}
@ The next regions are: Table of Font Segments, Future Information, String
Pool and Kerning Information. These are followed by the Character Definition
Region, which contains details of the rasters for each character's glyph.
Each character definition commences with |fnt_char_pars| of parameter
information; these are accessed through the offsets defined below.
In this version of the standard, each character is allocated 16 bytes of
parameter information.
@d fnt_char_pars=16 {Size of character parameters --- fixed}@#
@d fnt_kern_index=0 {Zero, or index into kerning table}
@d fnt_char_flags=fnt_kern_index+1 {Only 24 bits of flags}
@d fnt_char_width=fnt_char_flags+3 {In LN03 centipoints (or ``Gutenbergs'')}
@d fnt_left_bearing=fnt_char_width+4 {White space to left of raster}
@d fnt_raster_baseline=fnt_left_bearing+4 {Baseline relative to top of raster}
@ The character's parameters are followed by the encoded character raster:
this itself contains a number of counts preceding the rasters themselves; the
following definitions facilitate access to these fields.
@d fnt_char_orient=0 {0=Portrait, etc.}
@d fnt_char_type1=fnt_char_orient+1 {81H=Aligned expanded bit string}
@d fnt_char_type2=fnt_char_type1+1 {Unused if |fnt_char_type1| = 81H}
@d fnt_char_rows=fnt_char_orient+4 {Number of rows in raster}
@d fnt_char_cols=fnt_char_rows+2 {Number of columns in raster}
@d fnt_char_raster=fnt_char_cols+2 {Where rasters actually start}
@* Locating files containing character definitions. This procedure
determines the ``name'' of the required pixel file, and compares it with that
which is currently open, if any. If these names differ, we close the currently
open file, if any, and open the new one. Although the file will have already
been opened and checked for validity during font file generation, we still
perform the checks, because that also sets certain pointers correctly.
This procedure is used by |print_glyph| to output a bitmap image of any
oversized rasters. Note that it \&{has} parameter list |( TeX_font : integer)|;
this is omitted here because it has already been declared |forward|.
Note also that the variable |open_file_name| referenced is the \&{global}
variable of the same name, used by |print_glyph|, and not the identically
named formal parameter of |add_txf_to_lnf|. The same name has to be used
because these \.{WEB} code sections incorporated are used in that function
also.
@p procedure change_pixel_file; {|change_pixel_file(TeX_font : integer);|}
var @!new_file : file_spec;
i: integer; {Used when reading the directory}
begin
name_pxl_file(TeX_font,new_file,pxl_ident);
if new_file<>open_file_name then
begin
if (open_file_name<>'') then
close(pxl_file,@=disposition@>:=@=save@>,VAX_continue);
open_file_name:=new_file;@/
@<Open the file containing the glyph bitmaps@>;
if pxl_ident=pixel_file then begin
@<Check validity of PXL file@>;
@<Read character directory from |pxl_file|@>
end else begin
@<Read preamble@>;
skip_specials
end
end
end;
@* Creating the font file. The following function allocates the used
characters of the |TeX_font| to the predetermined locations in the |ln_font|;
the character rasters are taken from the LN03 font file |open_file_name|, which
has been set up to include the appropriate directory and file-name. After each
font file has been constructed in memory, it is converted to sixel format (See
below) and added to the records in the \.{LN3} file |ln3_file|.
The use of each of the parameters and local variables is as follows:
\yskip\hang|ln_font| The serial number of the LN03 font being generated.
Should a font have to cross the boundaries between internal fonts, this will
be updated (it's not a |var| parameter, so won't upset anything in the
caller). It is used in the procedure as a cross-check that the character
glyph currently being considered actually belongs to the font being created.
\yskip\hang|TeX_font| The identifying number of the \TeX\ font;
|font_num[TeX_font]| is that given by the \.{DVI} file.
\yskip\hang|open_file_name| Full file specification from which the rasters
should be read.
\yskip\hang|file_type| Indicates whether the |open_file_name| contains a
|packed_file| or a |pixel_file|.
\yskip\hang|rasters| Counts the number of bytes created in the current
|font_file|. The variable is initialized to four times the number of
characters added since each character requires a four-byte character locator
in the font file directory region.
\yskip\hang|i| and |j| General loop counters.
Further local variables will be declared for the function as necessary.
@p function add_txf_to_lnf(ln_font,TeX_font:integer;
open_file_name:file_spec;
file_type:pixel_types):integer;
var @<Local variables of |add_txf_to_lnf|@>@; {will be declared later}
@!rasters:integer; {bytes added to font}
@!i,@!j:integer; {general purpose pointers}
@t\4@>@<Local procedures of |add_txf_to_lnf|@>@;
begin
if file_type<>unavailable then
@<Open the file containing the glyph bitmaps@>;
@<Find name and magnification of font file@> ;
case file_type of
pixel_file:
@<Prepare to use \.{PXL} file@>;
packed_file:
@<Prepare to use \.{PK} file@> ;
unavailable:
begin
font_type[TeX_font]:=missing;
open_file_name := '[unavailable]'
end;
endcases;
max_ctr := font_ec[TeX_font];
@<Report name of font file in use@> ;
if file_type=unavailable then
write(' unavailable; printing rules for glyphs;');
print(' with '); write_ln(term_out,' loaded with ');
@<Determine lowest and highest characters used in the \TeX\ font@>;
rasters:=4*font_occupancy[TeX_font];
@<Cycle through the characters of |TeX_font|, downloading as required@> ;
@<Tidy up after copying rasters from \.{PXL} file@>;
end; {|add_txf_to_lnf|}
@ These variables used to be local to |add_txf_to_lnf|; however, some W{\mc
EB} modules invoked therein are now also used externally, but reference the
variables. Therefore they are now global.
\yskip\hang|percent| Gives the magnification of the current font, for use in
font loading reports. (An unmagnified font is at 100\%.)
\yskip\hang|name| Holds the ``\TeX-oriented'' font name, for inclusion in
reports on the terminal, whilst the full file specification (from
|open_file_name|) is reported in the log file.
@<Glob...@>=
@!percent:integer;
@!name: file_spec;
@!name_ptr: 0..name_size; {pointer for indexing |name|}
@ As each font is downloaded, we want to report its name and its magnification
(if not \.{\BS magstep 0}) on the terminal, whilst the logfile will receive
the full file specification. So we can commence by saving the name of the
font file, and the percentage magnification.
@<Find name and magnification of font file@>=
name := '';
for name_ptr:=font_name[TeX_font] to font_name[TeX_font+1]-1 do
name := name + xchr[names[name_ptr]]; {ASCII representation of font name}
percent:=round((mag*0.1*font_scaled_size[TeX_font])/font_design_size[TeX_font]);
@ With rasters held in an unpacked pixel file, we need to check the file's
validity, and then transfer the directory information into |pxl_dir|.
@<Prepare to use \.{PXL} file@>=
begin
@<Check validity of PXL file@>;
@<Read character directory from |pxl_file|@>
end
@ On the other hand, with packed pixel files, we need to read through the
preamble and skip over any specials which precede the first character raster
in the file.
@<Prepare to use \.{PK} file@>=
begin
@<Read preamble@>; skip_specials;
if font_check_sum[TeX_font]<>checksum then
warning('checksum doesn''t match in PK file')
@.checksum doesn't match in PK file@>
end
@ Once we've successfully opened the font file, we can report its name (and any
non-standard magnification).
@<Report name of font file in use@>=
print('Font ',name,' (at ',percent:1,'%) loaded from ',open_file_name);
write(term_out,'Font ',name);
if percent<> 100 then write(' (at ',percent:1,'%)');
@ We now set about creating (a portion of) an LN03 font file from the required
characters of |TeX_font|. We assume at first that it will be possible to pack
the entire |TeX_font| into a single LN03 font, but may need to revise this
opinion if the font turns out to be longer than 188 characters.
A |while| control construct is used instead of the obvious |for| loop, because
there may be a necessity for premature termination of the loop.
@<Cycle through the characters of |TeX_font|, downloading as required@>=
char_ctr:=font_bc[TeX_font];
while (char_ctr<=max_ctr) do
begin
@<Download character of font@>;
incr(char_ctr);
end;
@<Verify that all glyphs were available@>;
@ For each character in the |TeX_font|, we need to see whether it has been
marked as being used, and, if so, whether it belongs to the |ln_font|
currently being created.
@<Download character of font@>=
with glyph_map(TeX_font)(char_ctr) do
if loaded<>unused then
begin
@<Decide whereabouts to download the character@> ;
@<Finish off any previous half font on LN03@> ;
case file_type of
packed_file:
@<Copy glyph from packed file, and find the next one@>;
pixel_file:
copy_char(char_ctr,to_where);
unavailable:
@<Generate dummy directory entry for font that won't be used@>;
endcases;
end
@ We need to decide whereabouts to put this character glyph in the LN03 font
file being created. In the case of unpacked pixel files, we can simply use
the mapping determined at an earlier stage. However, the glyphs in packed
pixel files appear in the same order as the original \MF\ source, so it is
necessary to download them to the LN03 in the order of arrival, otherwise we
may find ourselves jumping between the ``left-hand'' and ``right-hand'' parts
of the code table.
It is at this point that we update |first_txfc| to the next available slot in
the |ln_font|, if necessary, to skip over the non-existent characters between
|left_last| and |right_first|, or from |right_last| to the beginning of an
overflow font (if we're using packed files; unpacked ones, being restricted
to 128 characters, can never require an overflow).
@<Decide whereabouts to download the character@>=
if first_txfc=left_last+1 then
first_txfc:=right_first
else if (first_txfc=right_last+1) and (file_type=packed_file) then
@<Prepare for overflow portion, if necessary@>;
to_where:=first_txfc
@ If a font is completely unavailable (perhaps the user has requested it at
a magnification not provided on the current system), we still have to go
ahead and `complete' the font for the LN03, because the font file's headers
already reflect the previously determined mapping. However, we shall
\\{never} image characters from the font, so we just make the character
locator entries in the font's ``directory'' zero.
@<Generate dummy directory entry for font that won't be used@>=
begin
@<Create null character locator entry@>;
loaded:=yes
end
@ Sometimes we find that we cannot complete the bitmap information relating
to a particular character, because the glyph is too large, or missing (or
the font itself is missing). Since we will already have completed the LN03
font's description relating to number of characters, etc., we must still
provide an entry in the font directory; the LN03 will not attempt to image a
character for which the directory entry is zero, so that's what we put
there!
@<Create null character locator entry@>==
begin
lnf_long(fnt_char_dir+4*(to_where-dir_base),0);
decr(num_chars); incr(null_chars)
end
@ Whenever we first meet a character taken from the ``right-hand'' half of the
LN03 font (the set designated as GR, in ISO2022/ANSI X3.64 parlance), we need
to finish off the half font which has been created in the left-hand table.
Similarly, if we meet a character from the ``left-hand'' half (GL) when we had
previously been adding characters to the GR set, it implies that we are
commencing output, to GL, of characters taken from a \TeX\ font which was too
large to be loaded into the 188 characters of the previous |ln_font|.
The font file to date is written out and a new one initialized to hold the GR
character set. Such successive font files are separated from each other by a
simple `\.,' character in the \.{LN3} file.
@<Finish off any previous half font on LN03@>=
if ((not using_GR) and (to_where>=right_first)) or
(using_GR and (to_where<=left_last)) then
begin output_LNF;
write_ln(ln3_file,',');@/
using_GR:=not using_GR;
init_LNF
end
@ If glyphs are being taken from a file of packed pixel rasters, we need to
copy the glyph to the LN03 file as the raster is built up from the file.
Having thus disposed of this glyph, we then scan through the file until
finding the \&{next} wanted glyph.
@<Copy glyph from packed file, and find the next one@>=
begin
repeat
@<Unpack and write...@>;
skip_specials
until (glyph_map(TeX_font)(char_num).loaded<>unused) or
(flag_byte=pk_post);
with glyph_map(TeX_font)(char_num) do
if loaded<>unused then
begin
char_code:=first_txfc;
font_code:=ln_font;
incr(first_txfc) {This might later be corrected}
end;
if flag_byte=pk_post then char_ctr := 255 {Break out}
end
@ Here are some local variables of |add_txf_to_lnf|, used in the above code:
\yskip\hang|char_ctr| Used to cycle through all possible character codes in
the |TeX_font|.
\yskip\hang|max_ctr| Unpacked pixel files (by their very architecture) can
contain a maximum of 128 glyphs; packed files (in this implementation) may
contain up to 256. This variable permits more efficient operation by limiting
the values assumed by |char_ctr|.
@<Local variables of |add_txf_to_lnf|@>==
@!char_ctr:integer;
@!max_ctr:integer;
@ After having downloaded the last character of |TeX_font| which belongs in
|ln_font|, we arrange things so that any further characters will be loaded into
|ln_font+1|. We accordingly want to start at |left_first| (which ought to
be the value held in |txfc_next|), and ensure that the font is written out to
the GR part of |ln_font|, before incrementing to the next font.
This code can only be invoked when working with packed pixel files, because
fonts which run over more than one LN03 font must exceed 188 characters in
length, and unpacked pixel files are limited to 128 characters!
@<Prepare for overflow portion, if necessary@>=
begin
incr(ln_font);
last_txfc := final_txfc; {|first_txfc>right_last|, therefore\dots}
@<Are we starting or finishing an LN03 font ``file''?@>; {|begin_txf:=false|}
first_txfc := txfc_next {This will be our next character}
end
@ The first job |add_txf_to_lnf| has to do is to open the (packed or
unpacked) pixel file; the file directory and font name are passed in to the
function (as parameter |open_file_name|).
Note that this \.{WEB} section is expanded by \.{TANGLE} in the context of
procedure |change_pixel_file| \&{and} of function |add_txf_to_lnf|. In the
first context, the string variable |open_file_name| referenced herein is the
\&{global} variable of that name, whilst within |add_txf_to_lnf| it is the
formal parameter of that function.
Assuming the file is opened succesfully, the size of the file is taken from
variable |file_len|, which is set up by the \.{user\_action} procedure with
which the file is opened.
The first block of the file is then read (into the associated file variable
|pxl_file|).
@<Open the file containing the glyph bitmaps@>=
begin open(pxl_file,open_file_name,@=readonly@>,@=access_method@>:=@=direct@>,
@=user_action@>:=file_open,VAX_continue);
file_stat:=status(pxl_file); ask:=file_stat<>0;
if ask then
begin
case file_stat of
2: file_message := 'PAS-E-ERRDUROPE';
3: file_message := 'PAS-E-FILNOTFOU';
5: file_message := 'PAS-E-ACCMETINC';
6: file_message := 'PAS-E-RECLENINC';
7: file_message := 'PAS-E-RECTYPINC';
8: file_message := 'PAS-E-ORDSPEINC';
othercases
file_message := str_int(file_stat);
endcases;
abort('Couldn''t open ',open_file_name,'. Status=',file_message);
@:fatal error Couldn't open file}{\quad\.{Couldn't open file} (pxl/pk)@>
end;
reset(pxl_file)
end
@ We apply some simple checks to determine whether the \.{PXL} file passed in
has a valid format. At the same time, we set up the local variable |pxl_size|
which gives the total length of the \.{PXL} file, in bytes, excluding the
identification longword of 1001 at its end.
@<Check validity...@>=
pxl_size:=file_len;
pxl_block:=0;
move_to_pxl(pxl_size-4);
i:=pxl_ptr+4; {Point {\bf after} the last block}
repeat
i:=i-4
until (i<0) or (pxl_long(i)=pxl_id);
pxl_size:=i+VAX_block_length*pxl_block;
if i<0 then abort('PXL file has bad format');
@:fatal error PXL file has bad format}{\quad\.{PXL file has bad format}@>
@ Having found the end of the \.{PXL} file, we are able to extract the
last four longwords. These give us, starting with the last, the longword
address of the start of the character directory within the file, the ``design
size'' (in scaled points), the ``magnification'' of the font (will be 1500
for fonts designed for 300 pixels/inch devices) and the font checksum.
This latter quantity should match that read from the \.{TFM} file by \TeX.
After using these longwords, we then go to the first byte of the character
directory, and copy it into |pxl_dir|. Each character's entry consists
of the following fields:
\yskip\hang|num_cols| the number of pixels printed for each row of the
character;
\yskip\hang|num_rows| number of rows of pixels printed;
\yskip\hang|x_off| offset (in pixels) of the left-hand edge of the raster,
relative to the reference point;
\yskip\hang|y_off| similarly for top of raster relative to the baseline;
\yskip\hang|addr| the longword address within the \.{PXL} file of the start
of the rasters;
\yskip\hang|tfm_width| the character's width, in the same format as for
|tfm_file|.
@<Read character directory from |pxl_file|@>=
move_to_pxl(pxl_size-16);
if font_check_sum[TeX_font]<>long_pxl(pxl_ptr) then
warning('checksum doesn''t match in PXL file');
@.checksum doesn't match in PXL file@>
magnification:=long_pxl(pxl_ptr);
dsize:=long_pxl(pxl_ptr);
dsize:=(dsize/two_to_the_20th)*(magnification/1500.0);@/
move_to_pxl(long_pxl(pxl_ptr)*4); {Go to start of directory}
for i:=0 to 127 do
with pxl_dir[i] do
begin num_cols:=word_pxl(pxl_ptr);
num_rows:=word_pxl(pxl_ptr);
x_off:=sign_pxl_word(pxl_ptr);
y_off:=sign_pxl_word(pxl_ptr);
addr:=long_pxl(pxl_ptr);
tfm_width:=long_pxl(pxl_ptr)
end
@ We need to determine the lowest and highest ordinal character positions of
those characters used in the \TeX\ font being encoded into the LN03 font file;
the variables |first_txfc| and |last_txfc| are used for this purpose. If there
are no characters used at all in the \TeX\ font some malfunction must have
occurred, because |add_txf_to_lnf| should not have been invoked under those
circumstances.
The character numbers will be those of the LN03 font file, having already been
mapped to these during the mapping phase; therefore, if the lowest character
number used is |= left_first| then we must be starting a new font, so must
initialize the empty font file and indicate that this is a |GL| font
(left-hand half of eight-bit character table).
@<Determine lowest and highest characters used in the \TeX\ font@>=
first_txfc:=-1; last_txfc:=-1; {For |ln_font|}
txfc_next:=-1; final_txfc:=-1; {and for |ln_font+1|}
for i:=first_glyph_index(TeX_font) to last_glyph_index(TeX_font) do
with glyphs[i] do
@<Note |last_txfc|, and set |first_txfc| for first character@> ;
@<Are we starting or finishing an LN03 font ``file''?@>
@ As we cycle through all the allocated characters, we note each code in turn,
thus determining the highest code used. We also note the lowest one when we
meet the first code used. If the |TeX_font| extends into a second |ln_font|,
the corresponding information will be recorded in |final_txfc| and |txfc_next|,
respectively.
@<Note |last_txfc|, and set |first_txfc| for first character@>=
if loaded = wanted then
begin if txf_to_lnf[font_code]=ln_font then
begin if first_txfc=-1 then first_txfc:=char_code;
last_txfc:=char_code; font_code:=ln_font
end
else {This must be overflow of |TeX_font| into |ln_font+1|}
begin if txfc_next=-1 then txfc_next:=char_code;
final_txfc:=char_code; font_code:=ln_font+1
end
end
@ If the lowest character code used is |left_first|, then we shall have to
initialize a new LN03 font file. Similarly, if the last character used is the
last character allocated to this LN03 font, we shall later have to finish off
the latter. We therefore note these two conditions in |begin_txf| and
|end_txf|, and initialize the font ``file'' if the former condition pertains.
@<Are we starting or finishing an LN03 font ``file''?@>=
if last_txfc<0 then
abort('Internal error (empty font)');
@:fatal error Internal error empty font}{\qquad\.{(empty font)}@>
begin_txf:=first_txfc=left_first;
end_txf:=last_txfc=last_lnf[ln_font];
if begin_txf then
begin using_GR:=false;
init_LNF
end
@ Here are some more local variables of |add_txf_to_lnf|, used in the above
code:
\yskip\hang|first_txfc| The lowest character code allocated to a character of
|TeX_font| when the mapping took place.
\yskip\hang|last_txfc| Similarly, the highest code used.
\yskip\hang|txfc_next| The lowest character code for a character of |TeX_font|
allocated to |ln_font+1| when the mapping took place.
\yskip\hang|final_txfc| Similarly, the highest code used by |TeX_font| in
|ln_font+1|
\yskip\hang|begin_txf| A boolean, which is |true| if this |TeX_font| commences
a new font file in the LN03.
\yskip\hang|end_txf| This is |true| if the last character of the present
|TeX_font| is the last character mapped to |ln_font|.
@<Local variables of |add_txf_to_lnf|@>==
@!first_txfc, @!last_txfc : integer;
@!txfc_next, @!final_txfc : integer;
@!begin_txf, @!end_txf : boolean;
@ After all the required character glyphs have been copied from the \.{PXL}
file, we can close it.
If, and only if, the very last character code was that of the highest mapped
into this font, we can output the LN03 font to the \.{LN3} file.
Finally, we report the number of bytes of rasters created in the font file.
@<Tidy up after copying rasters from \.{PXL} file@>=
close(pxl_file,@=disposition@>:=@=save@>,VAX_continue);
if end_txf then output_LNF;
print_ln(rasters:0,' bytes of rasters (',font_occupancy[TeX_font]:1,
' characters)');
write_ln(term_out,rasters:0,' bytes of rasters (',font_occupancy[TeX_font]:1,
' characters)' ,crlf);
add_txf_to_lnf:=rasters {Total bytes of rasters added to file}
@* Procedures to access the font file. The definitions which follow facilitate
access to the individual bytes, words, longwords and strings of the
``|lnf_file|'' by accessing the appropriate entity in the variant record
representation of the file's blocks. The procedures allow us to write bytes,
words and longwords into the memory representation of the |lnf_file|.
@d lnf_bytes(#)==lnf[(#) div VAX_block_length].bytes[(#) mod VAX_block_length]
@d lnf_words(#)==lnf[(#) div VAX_block_length].words[((#) mod VAX_block_length)
div 2]
@d lnf_longs(#)==lnf[(#) div VAX_block_length].longs[((#) mod VAX_block_length)
div 4]
@d lnf_str(#)==lnf[(#) div VAX_block_length].str[((#) mod VAX_block_length) div
4]
@<Local procedures of |add_txf_to_lnf|@>=
procedure lnf_byte(byte_address:integer; byte_val:eight_bits);
begin
lnf_bytes(byte_address):=byte_val
end;
procedure lnf_word(byte_address:integer; word_val:sixteen_bits);
begin if odd(byte_address) then
abort('Internal error (word at odd byte ',byte_address:1,')');
@:fatal error Internal error word at odd byte}{\qquad\.{(word at odd byte)}@>
lnf_words(byte_address):=word_val
end;
procedure lnf_long(byte_address,long_val:integer);
begin if odd(byte_address) then
abort('Internal error (longword at odd byte ',byte_address:1,')');
@:fatal error Internal error longword at odd byte}{\qquad\.{(longword at odd byte)}@>
if byte_address mod 4 = 0 then
lnf_longs(byte_address):=long_val
else
begin if long_val<0 then
begin long_val:=long_val+@'17777777777; {$2^{31}-1$}
long_val:=long_val+1;
lnf_words(byte_address+2):=(long_val div 65536)+32768
end else
lnf_words(byte_address+2):=(long_val div 65536);
lnf_words(byte_address):=long_val mod 65536
end
end;
@ When we are ``outputting'' to the |lnf_file| from \.{PK} files, it's more
convenient to call the following procedure; in fact, it might prove handy for
use in |copy_char| too!
@<Additional low-level...@>=
procedure put_lnf(@!item : integer);
begin lnf_bytes(base):=item;
incr(base)
end;
@#
procedure repeat_row;
begin put_lnf(lnf_bytes(row_addr));
incr(row_addr)
end;
@ For downloading the bitmap of an oversized glyph, we need to ``read'' six
rows of pixels from either the |pxl_file| itself, or from the character's
bitmap which has been formed in |lnf_file| from the |packed_file|.
@<Read rows of pixels into sixel buffer@>=
if pxl_ident=packed_file then
for j:=1 to ((num_cols+7) div 8) do
begin
sixel_line[k,j]:=reverse[lnf_bytes(base)];
incr(base)
end else
begin
for j:=1 to (num_cols+7) div 8 do
begin
sixel_line[k,j]:=pxl_byte(pxl_ptr);
incr(pxl_ptr); must_get(pxl_ptr)
end;
while (pxl_ptr mod 4) <> 0 do
begin
incr(pxl_ptr); must_get(pxl_ptr)
end
end
@ It could happen that a glyph is imaged that doesn't exist in the font; we
then mark this in the |loaded| field of the |glyph_map| as |missing|; such
glyphs will result in appropriate $(x,y)$ positioning commands being output
to the LN03, by derivation from the font metrics data.
@<Other values of |download_status|@>= ,@!missing
@ After we've read through the (packed or unpacked) pixel file, we should have
managed to download glyphs for all the characters used in the current document.
If the following check finds a character without an associated glyph, it
downloads a dummy, invisible glyph of the appropriate width.
@<Verify that all glyphs were available@>=
for char_num := font_bc[TeX_font] to font_ec[TeX_font] do
with glyph_map(TeX_font)(char_num) do
if loaded=wanted then
begin
loaded := missing; {Ensure we don't try to image the glyph}
term_offset := 20; {Ensure we get a newline before warning}
warning('Font ',name,' doesn''t contain character ',char_num:1,crlf);
@:Warning: Font doesn't contain character}{\quad\.{Font doesn't contain...}@>
@<Decide whereabouts to download the character@>;
@<Finish off any previous half font on LN03@>;
tfm_width := width;
num_cols := pixel_width;
base := ras_beg + ras_len;
@<Create null character locator entry@>;
char_code:=first_txfc; font_code:=ln_font
end
@*Copy one character's rasters. For each character taken from an unpacked
(\.{PXL}) file, the function |add_txf_to_lnf| calls this procedure |copy_char|,
which
provides the main action of the function, by converting the \.{PXL} file
rasters to the format required by the \.{LN03}, and storing them away in the
appropriate part of the ``|lnf_file|'' in memory.
The global variable |base| is used as a pointer into the appropriate byte of the
Character Definitions Region of the LN03 font file, and commences at the
location determined from the ``size so far'' as recorded by |ras_beg| and
|ras_len|.
Having verified that the \.{PXL} file contains the character glyph required
(if the character is absent, the address field of the directory entry will not
point to an address within the file), the |move_to_pxl| procedure is called to
make the appropriate bytes of the characters rasters accessible to the
program.
The character parameters section of the character definition is then completed
in the font file; the first four bytes contain the kerning information (null,
because \TeX\ does all the kerning for us, and we don't want the LN03 to upset
it) and 24 bits of flags --- the only bit that we set in this flag field is
the `flag flag' bit.
LN03 pixels (of which there are 300 to an inch) can be converted to DEC
centipoints (or Gutenbergs) by multiplying by 7200/300 = 24.
@d Pxl_to_Gut=24
@<Local procedures of |add_txf_to_lnf|@>=
procedure copy_char(char_num,to_where:integer);
var @!i,@!j,@!k,@!n,@!len:integer;
begin
base:=ras_beg+ras_len; {Where to start saving the parameters/rasters}
with pxl_dir[char_num] do
if (addr>0) and (addr<=(pxl_size div 4)-8) then
{Only process characters which lie within the file!}
with glyph_map(TeX_font)(char_num) do
begin
move_to_pxl(addr*4);
char_code:=to_where;@/
@<Process |tfm_width| information@>;
@<Validate size of character's glyph@>;
@<Complete remainder of Character Definition parameters@>;
@<Copy the rasters themselves@>;
@<Calculate the number of bytes of rasters transferred@>;
@<Complete the entry in the font character directory@>
end {|with|}
end;
@ The true character width (in scaled points) is taken from the |tfm_width| field of
the pixel file directory; this value is converted to pixels and stored in the
|ch_widths| array, where it is used by the procedures which set characters on
the page. The value is also converted to Gutenbergs and stored in the
|fnt_char_width| field for the character raster in the font file. If the
width differs from that computed from the \.{TFM} file by more than one pixel,
this is reported, and the value derived from the \.{PXL} file substituted in
preference.
@<Process |tfm_width| information@>=
ch_wid:=round((dsize*resolution*tfm_width)/(two_to_the_20th*72.27));
lnf_word(base,@"0000); {No kerning, lowest byte of flags=0}
lnf_word(base+2,@"8000); {Highest bit of flags (flag flag) = 1}
lnf_long(base+fnt_char_width,trunc(ch_wid*(7200.0/resolution)));
ch_widths[ln_font,to_where]:=ch_wid;
tfm_wid:=pixel_width;
if abs(ch_wid-tfm_wid) > 1 then
begin warning('Pixel width discrepancy; font ',open_file_name,' char ',char_num:1);
@:Warning: Pixel width discrepancy}{\quad\.{Pixel width discrepancy}@>
show('---computed width: ',tfm_wid:1,'; actual width: ',ch_wid:1);
end;
pixel_width:=ch_wid
@ The above section of code needs some workspace in which to perform those width
computations. They may as well be local to |add_txf_to_lnf|.
\yskip\hang|ch_wid| Holds the actual width of the character, in pixels,
derived from the |tfm_width| field of the pixel file's directory.
\yskip\hang|tfm_wid| Holds the character width as calculated from the
|ch_widths| entry which came from the TFM file for this font.
@<Local variables of |add_txf_to_lnf|@>==
@!ch_wid,@!tfm_wid:integer;
@ The \.{PXL} file directory entry contains |x_off| and |y_off| fields, which
define the starting point of the raster relative to the character's reference
point. These are passed to the font file as the |fnt_left_bearing| and
|fnt_raster_baseline| fields of the raster entry, in Gutenbergs.
The flags longword of the raster is then created, and indicates that the
raster is in portrait orientation (relative to memory, not to the output
device) and is an aligned expanded bit string. (Unfortunately, although
DEC Std 180 permits run-length encoding of rasters, the LN03 itself does not
support this facility, so rasters have to be downloaded in fully expanded form.)
Invisible fonts (used by Sli\TeX) will have the bounding box info (|num_cols|
and |num_rows|) both equal to zero. Since the LN03 isn't very happy with such
characters (!), we will later insert a null character locator in the font's
directory; we still go through the motions of generating character
parameters and the (empty) bitmap in the font file, but bytes occupied by
these will be overwritten with the next real bitmap. Although instructions
to image such a null character are accepted by the printer, unfortunately
the printer seems to image some other character at random! Therefore we
mark the glyph as |missing|, so that no attempt is made to image it later.
Oversized glyphs are also not inserted into the font file; so it is only for
genuine printable glyphs that the value of |base| will be advanced to
reserve the space occupied by the character's parameters.
This module is always called in the context |with
glyph_map(TeX_font)(char_num) do|.
@<Complete remainder of Character Definition parameters@>=
lnf_long(base+fnt_left_bearing,int(-Pxl_to_Gut*x_off));
lnf_long(base+fnt_raster_baseline,int(-Pxl_to_Gut*y_off));
lnf_long(base+fnt_char_pars+fnt_char_orient,@"00008100); {Aligned,bit-expanded}
if num_rows=0 then loaded := missing; {``Invisible'' glyph}
lnf_word(base+fnt_char_pars+fnt_char_cols,num_cols);
lnf_word(base+fnt_char_pars+fnt_char_rows,num_rows);
if loaded=yes then
base:=base+fnt_char_pars+fnt_char_raster
@ Here are some further values than can be allocated to the |loaded| field
of |glyph_map|: values could be:\par
\item{$\bullet$} |yes| if character's glyph was included in a downloaded LN03
font;
\item{$\bullet$} |no| if the glyph exceeded the LN03's capability for the
area of a downloaded glyph. In this case a null character locator will have
been inserted in the directory of the downloaded font, and the actual
glyph's bitmap will be sent to the printer each and every time it occurs in
the document.
@<Other values of |download_status|@>= ,@!yes ,@!no
@ The LN03 has a physical (apparently arbitrary) restriction on the maximum size
of a downloadable glyph. For a glyph to be acceptable, it must satisfy the
inequality $$
2\times\lceil|num_cols|/2\rceil\times\lceil|num_rows|/8\rceil\le5700$$
The following section prevents such an oversized glyph from being included in
the downloaded font; it is always called in the context |with
glyph_map(TeX_font)(char_num) do|.
@<Validate size of character's glyph@>=
if ((num_rows+7) div 8) * 2 * ((num_cols+1) div 2) > largest_glyph then
loaded:=no {Indicate oversized glyph unavailable}
else
loaded:=yes {Indicate glyph successfully downloaded}
@ After further setting up of values in the font file, we copy the bits of the
rasters from the \.{PXL} file to the LN03 font file; this requires that we
reverse each byte.
As each byte is copied, we get another; the |must_get| procedure will read in
a new block from the \.{PXL} file if necessary. At the end of each row, the
next is started from the next complete longword of the \.{PXL} file.
Only character glyphs which are capable of being downloaded are entered into
the file; invisible and oversized glyphs don't need to have the bitmap
copied, and they'll eventually end up with a null character locator in the
font file's directory.
This module is always called in the context |with
glyph_map(TeX_font)(char_num) do|.
@<Copy the rasters themselves@>=
if loaded=yes then
for i:=1 to num_rows do
begin
for j:=1 to (num_cols+7) div 8 do {Whole bytes required}
begin lnf_bytes(base):=reverse[pxl_byte(pxl_ptr)];@/
incr(pxl_ptr); must_get(pxl_ptr); incr(base)
end; {Copied bytes for one row of pixels}
while (pxl_ptr mod 4)<>0 do
begin incr(pxl_ptr); {Row held in 32-bit words in \.{PXL} file}
must_get(pxl_ptr)
end
end
@ The |copy_char| procedure requires an array to be set up to ``reverse''
individual bytes' bits, since the \.{PXL} file stores the first pixel of
a row at the most significant bit of the long-word, whilst DEC Std 180
requires the first pixel to be the least significant bit of the first byte.
We therefore create a global array |reverse|, which we initialize with
appropriate patterns to reverse any byte proferred as an index.
@<Globals...@>=
@!reverse: packed array [0..255] of eight_bits;
@ @<Procedures for...@>=
procedure Generate_Reversal;
var @!index,@!patt,@!cnt,@!rev: eight_bits;
begin
for index:=0 to 255 do
begin rev:=0; patt:=index;
for cnt:=1 to 8 do
begin rev:=2*rev;
if odd(patt) then rev:=rev+1;
patt:=patt div 2
end;
reverse[index]:=rev
end;
end;
@ @<Set initial...@>=
Generate_Reversal;
@ The number of bytes required for the character glyph in landscape and
portrait modes is computed, and added to the accumulated sum of these for all
characters; the ``mixed'' mode count is increased by the larger of these
quantities.
The value |len| is also computed to be added to the Character Definitions
descriptor held in |ras_beg,ras_len|. None of these quantities are updated
unless the character glyph has actually been copied into the font file,
i.e., |loaded=yes|.
@<Calculate the number of bytes of rasters transferred@>=
if loaded=yes then
begin
i:=((num_rows+7) div 8)*num_cols; {Bytes required for raster in landscape mode}
lsize:=lsize+i;
j:=((num_cols+7) div 8); {Bytes/column}
psize:=psize+j*num_rows; {bytes added to portrait rasters}
msize:=msize+VAX_max(i,j*num_rows); {bytes added to mixed mode rasters}
len:=j*num_rows+fnt_char_pars+fnt_char_raster {Allow for the parameters}
end else len:=0
@ After transferring the rasters for one character's glyph, we finally
complete the entry in the font file's character directory. We record the
address of the character's rasters in the appropriate character locator,
update the count of rasters stored, and round up to the next \\{word}
boundary in the font file (note that it is not a requirement of DEC Std 180
that rasters start on a \\{longword} boundary.)
If the glyph was not copied into the font file, because it exceeded the
LN03's maximum size of glyph, or because it was invisible, then a null entry
is made for the character locator, and the next raster, if any, will
overwrite that just stored.
@<Complete the entry in the font character directory@>=
if loaded<>yes then
begin
lnf_long(fnt_char_dir+4*(to_where-dir_base),0);
incr(null_chars); decr(num_chars)
end
else begin
lnf_long(fnt_char_dir+4*(to_where-dir_base),ras_beg+ras_len);
ras_len:=ras_len+len; rasters:=rasters+len;
if odd(ras_len) then
begin
lnf_byte(base,0); {Null byte to round raster to word boundary}
incr(ras_len);
incr(rasters)
end
end
@ The following procedure initialises the ``fixed'' area of the \.{LN03} font
file as held in memory. We start by clearing the whole of the first block of
the file; this is sufficient to ensure that the whole of the file header,
attributes and parameters regions are zeroed, which reduces the number of
individual assigments necessary.
The counts of space occupied by the rasters, and the raster sizes in portrait,
landscape and mixed modes are also initialized.
@<Local procedures of |add_txf_to_lnf|@>=
procedure init_LNF;
var @!i,@!j:integer;
begin
for i:=0 to 127 do lnf_longs(4*i):=0; {Clear the lnf header}
ras_len:=0; psize:=0; lsize:=0; msize:=0;@/
@<Complete the font file's header region@>@;
@<Complete the font file's attributes region@>@;
@<Complete the font file's parameters region@>@;
@<Complete the font file's string pool region@>@;
end;
@ The font file's header contains some identification material, and a number
of descriptors which point to other regions of the font file.
@<Complete the font file's header region@>=
lnf_str(fnt_identifier):='FONT';
lnf_long(fnt_version,1); {Font File Format is V1.0}
file_ID:='U0000'+@=dec@>(ln_font,2,2)+'002SK00GG0001UZZZZ02F000';
if using_GR then file_ID[21]:='O';
lnf_long(12,VAX_length(file_ID)); lnf_long(16,fnt_file_id);
for j:=1 to VAX_length(file_ID) do lnf_byte(j+fnt_file_id-1,xord[file_ID[j]]);
{For the moment, we set creation date to 11-SEP-1973 14:00:00}
lnf_word(fnt_date,1973); lnf_word(fnt_date+2,9); lnf_word(fnt_date+4,11);
lnf_word(fnt_date+6,14); lnf_word(fnt_date+8,0); lnf_word(fnt_date+10,0);
lnf_long(fnt_attr,fnt_attr_len); lnf_long(fnt_attr+4,fnt_attributes);
lnf_long(fnt_params,fnt_params_len); lnf_long(fnt_params+4,fnt_parameters);
if using_GR then
num_chars:=last_lnf[ln_font]-right_first+1
else
if last_lnf[ln_font]<left_last then
num_chars:=last_lnf[ln_font]-left_first+1
else
num_chars:=left_last-left_first+1;
lnf_long(fnt_char_directory,4*num_chars);
lnf_long(fnt_char_directory+4,fnt_char_dir);
lnf_pool_offset:=fnt_char_dir+4*num_chars;
lnf_long(fnt_seg_list,0); { Empty |seg_list| descriptor }
lnf_long(fnt_seg_list+4,0);
lnf_long(fnt_future,0); lnf_long(fnt_future+4,0);
lnf_long(fnt_strings,4*((fnt_string_pool_len+3) div 4)); {Rounded to longword boundary}
lnf_long(fnt_strings+4,lnf_pool_offset);
ras_beg:=lnf_pool_offset+4*((fnt_string_pool_len+3) div 4);
{We fill in address of Kerning Table, but leave it of length zero}
lnf_long(fnt_kerns+4,0);
lnf_long(fnt_chars+4,ras_beg); {We can't fill in length until finished}
lnf_long(fnt_first,left_first); lnf_long(fnt_last,num_chars-1+left_first);
{No definitions (Yet!!!!) for less than, greater than and error characters}
{No usage of Extension field}
lnf_long(fnt_space," "); {Space character value}
lnf_long(fnt_organisation,@"A8);
{Expanded rasters;Identical Orientation; Large Values used}
lnf_long(fnt_no_of_params,fnt_char_pars);
lnf_long(fnt_in_file_count,num_chars);
null_chars:=0; lnf_long(fnt_null_count,null_chars); {Null count maybe revised later}
lnf_long(fnt_char_count,num_chars);
{Alternate blocks remains at zero}
lnf_long(fnt_rasters,num_chars);
{Compressed rasters remains at zero; psize, etc., are filled in at end}
if using_GR then dir_base:=right_first else dir_base:=left_first;
{Establish offset into font's character locators}
@ These used to be local variables of |add_txf_to_lnf|, used in the
previous section. However, since we now require to use |num_chars| and
|null_chars| to update the font file before it is finally output, they have
to be global. (Strictly speaking, |file_ID| could remain local, but that
would require much shuffling of the W{\mc EB}!)
\yskip\hang|file_ID| Holds the fixed-length string which identifies any
particular font file to the LN03.
\yskip\hang|num_chars| Gives the number of characters defined in each half of
the LN03 font file.
\yskip\hang|null_chars| Counts any null character locators added to the file
for oversized or missing glyphs.
\yskip\hang|dir_base| Code of the first character loaded in the current half
font; used to derive the correct offset into |font_char_dir| for completing
locators.
@<Global...@>=
@!file_ID:packed array [1..31] of char;
@!num_chars,@!null_chars:integer;
@!dir_base:integer;
@ The font file's attributes region contains mostly descriptors (of strings in
the string pool region) which identify the font, font family, \\{etc.}
@<Complete the font file's attributes region@>=
lnf_long(fnt_attributes+fnt_attr_flags,2); {Roman}
lnf_long(fnt_attributes+fnt_char_set,fnt_designator_len);
lnf_long(fnt_attributes+fnt_char_set+4,lnf_pool_offset);
lnf_long(fnt_attributes+fnt_type_ID,fnt_type_ID_len);
lnf_long(fnt_attributes+fnt_type_ID+4,lnf_pool_offset+fnt_designator_len);
lnf_long(fnt_attributes+fnt_type_family,fnt_type_family_len);
lnf_long(fnt_attributes+fnt_type_family+4,
lnf_pool_offset+fnt_designator_len+fnt_type_ID_len);
lnf_long(fnt_attributes+fnt_font_ID,fnt_font_ID_len);
lnf_long(fnt_attributes+fnt_font_ID+4,
lnf_pool_offset+fnt_designator_len+fnt_type_ID_len+fnt_type_family_len);
{Type category, Font description descriptors empty}
{Following values are purely arbitrary at present; try harder later}
lnf_long(fnt_attributes+fnt_type_size,10); {``10pt''}
lnf_long(fnt_attributes+fnt_average_width,500); {5.00pt}
lnf_word(fnt_attributes+fnt_resolution+2,24); {24.0000 centipoints/pixel}
lnf_long(fnt_attributes+fnt_weight,16); {``Normal'' weight}
lnf_long(fnt_attributes+fnt_horiz_prop,16); {Neither expanded nor compressed}
lnf_word(fnt_attributes+fnt_horiz_ratio,1); {1:1 relative to a ``normal'' font}
lnf_word(fnt_attributes+fnt_horiz_ratio+2,1);
lnf_word(fnt_attributes+fnt_pixel_aspect,1); {``Square'' pixels}
lnf_word(fnt_attributes+fnt_pixel_aspect+2,1);
lnf_word(fnt_attributes+fnt_rotation+2,1); {tan = 0/1 = $0^\circ$}
{Device characteristics = 0}
{Foundry and Designer have empty descriptors}
@ The font file's parameters region contain information relating to all the
characters as a whole. Since they relate to such features as electronic
underlining, which are not utilized by the \.{LN3} file generated by
DVItoLN03, the values used are purely arbitrary. (The values are those used
by Flavio Rose's original \.{LN03toPP} (PL/I) program.)
@<Complete the font file's parameters region@>=
{Params flags = 0}
lnf_long(fnt_parameters+fnt_underline_offset,30);
lnf_long(fnt_parameters+fnt_underline_thickness,20);
lnf_long(fnt_parameters+fnt_thru_offset,-60);
lnf_long(fnt_parameters+fnt_thru_thickness,20);
lnf_long(fnt_parameters+fnt_overline_offset,-150);
lnf_long(fnt_parameters+fnt_overline_thickness,20);
lnf_word(fnt_parameters+fnt_slant+2,1); {tan = 0/1 = $0^\circ$}
lnf_word(fnt_parameters+fnt_shadow_offset+2,30); {$\delta x$ only}
lnf_long(fnt_parameters+fnt_superscript,-90); {$\delta y$ only}
lnf_long(fnt_parameters+fnt_subscript,40); {$\delta y$ only}
lnf_long(fnt_parameters+fnt_centreline,60);
lnf_long(fnt_parameters+fnt_min_space,240); {Note |min>max|!}
lnf_long(fnt_parameters+fnt_max_space,60);
lnf_long(fnt_parameters+fnt_mean_space,100);
lnf_long(fnt_parameters+fnt_em_width,240);
lnf_long(fnt_parameters+fnt_en_width,120);
lnf_long(fnt_parameters+fnt_thin_space,40);
lnf_long(fnt_parameters+fnt_digit_width,120);
lnf_long(fnt_parameters+fnt_topline_offset,-160);
lnf_long(fnt_parameters+fnt_accent,-120);
lnf_long(fnt_parameters+fnt_halfline_offset,-70);
lnf_long(fnt_parameters+fnt_overall_height,240);
lnf_long(fnt_parameters+fnt_height,-180);
lnf_long(fnt_parameters+fnt_depth,60);
lnf_long(fnt_parameters+fnt_H_height,160);
lnf_long(fnt_parameters+fnt_ex_height,120);
lnf_long(fnt_parameters+fnt_white_above,20);
lnf_long(fnt_parameters+fnt_white_below,20);
@ The string pool region of the font file contains the majority of the
character strings which are required to identify the font.
@<Complete the font file's string pool region@>=
lnf_byte(lnf_pool_offset,"0"); {Char set designator}
if using_GR then
lnf_byte(lnf_pool_offset+1,"<")
else
lnf_byte(lnf_pool_offset+1,"B");
lnf_byte(lnf_pool_offset+2,9); {HT character}
for i:=4 to fnt_designator_len do
lnf_byte(lnf_pool_offset+i-1,"Z"); {|'ZZZZ'|---full char set}
for i:=1 to fnt_type_ID_len do
lnf_bytes(lnf_pool_offset+i-1+fnt_designator_len):=
lnf_bytes(i-1+fnt_file_id); {Copy first 7 of |file_ID|}
for i:=1 to fnt_type_family_len do
lnf_bytes(lnf_pool_offset+i-1+fnt_designator_len@|+fnt_type_ID_len) :=
" "; {Type family is all spaces}
for i:=1 to fnt_font_ID_len do
lnf_bytes(lnf_pool_offset+i-1+fnt_designator_len@|+fnt_type_ID_len@|
+fnt_type_family_len) := lnf_bytes(i-1+fnt_file_id); {Copy first 16 of |file_ID|}
@ The following procedure is invoked whenever an \.{LN03} font file in memory
is ready to be written to the output |ln3_file|. Firstly we complete those
fields of the font file header region which we were unable to complete before,
storing away the total length (at front \&{and} back) and repeating the
`\.{FONT}' indication at the end of the file. We also complete the character
definitions region descriptor, and fill in the counts of bytes loaded as
portrait, landscape and mixed mode rasters.
Obviously it is not possible to include the file \\{verbatim} in the output as
a binary image, since the terminal output driver and/or the printer symbiont
would probably eliminate most of those characters which are unprintable.
Therefore the LN03 requires that the binary file be encoded into ``sixel''
format; this is more usually used to transfer graphics information to DEC
printers, one sixel being a printable character (in the range `\.?'$\to$`\.\~')
which encodes the numbers |0..63| representing a column of six pixels.
@<Local procedures of |add_txf_to_lnf|@>=
procedure output_LNF;
var @!i,@!j,@!k,@!records,@!sixel_ct:integer;
@!line_out:varying [128] of char;
@!transform : six_pack;
begin
lnf_len:=ras_beg+ras_len+8; {Make room for Trailing identifier}
lnf_long(fnt_total_size,lnf_len); {Total size noted into first longword}
lnf_long(lnf_len-8,lnf_len); {and into penultimate longword}
lnf_long(lnf_len-4,lnf_longs(fnt_identifier)); {Copy |'FONT'| from 2nd longword to last}
lnf_long(fnt_chars,ras_len); {Length of character definitions}
lnf_long(fnt_in_file_count,num_chars); {We might have excluded some}
lnf_long(fnt_null_count,null_chars); {which will be reflected here}
lnf_long(fnt_rasters,num_chars); {and so we only made this many rasters}
lnf_long(fnt_portrait,psize);
lnf_long(fnt_landscape,lsize);
lnf_long(fnt_mixed,msize);
@<Sixelize and write@>
end;
@ To convert eight-bit bytes to sixels, which the \.{LN03} requires, we use
(abuse?) \PASCAL's variant record mechanism, to map three bytes into 4 six-bit
characters.
@<Types in...@>==
@!six_pack = packed record
case boolean of
false: (@!bytes : packed array [0..2] of eight_bits);
true: (@!sixels: packed array [1..4] of [bit(6)] 0..63)
end;
@ The three bytes have to be placed in the record such that the earlier bytes
(least significant in the file) are at the left-ward end of the record. The
resultant six-bit integers are converted to ``printable'' characters by adding
the |ASCII| character `\.{?}', thus giving characters in the range
`\.{?}'$\to$`\.{\~}'. It is these characters that DEC calls ``sixels'', being
a representation of six pixels in one character.
It is most convenient to process 96 bytes at a time, thus yielding 128
characters records (of sixel characters). Accordingly, we firstly ensure that
the file is some multiple of 96 bytes long.
@<Sixelize and write@>=
while (lnf_len mod 96) <> 0 do
begin lnf_word(lnf_len,0); { Clear out remainder of ``last record'' }
lnf_len:=lnf_len+2
end; { Can't work in longwords, in case on odd word boundary }
records:=lnf_len div 96;
i:=0; j:=0; {Block/byte in |lnf| array}
while records > 0 do
begin
line_out:=''; {Null string}
sixel_ct:=0; {We write 128 sixels (=96 bytes) per record}
repeat
k:=2;
repeat
transform.bytes[k]:=lnf[i].bytes[j]; {Store bytes from left}
incr(j); decr(k);
while j>=512 do
begin incr(i); j:=j-512 end
until k<0; {3 bytes at a time}
for k:=4 downto 1 do
begin
line_out:=line_out+chr(transform.sixels[k]+"?");
incr(sixel_ct)
end;
until sixel_ct=128;
write_ln(ln3_file,line_out);
decr(records)
end
@* Skipping pages.
A routine that's much simpler than |do_page| is used to pass over
pages that are not being translated. The |skip_pages| subroutine
is assumed to begin just after the preamble has been read, or just
after a |bop| has been processed. It continues until either finding a
|bop| that matches the desired starting page specifications, or until
running into the postamble.
In this version of \.{DVItoLN03}, it is necessary to ``read'' through all
the pages twice; on the first pass, we gather statistics about the usage
of each character in every font, to minimize the number of rasters
which have to be loaded into the LN03. This procedure |skip_pages| has
therefore been modified to permit its use during this scan.
In such use, it is first called in the normal fashion, truly to skip pages
until |start_match| indicates that the first page to be output has been
found (or the beginning of the postamble, in the event that the specified
first page does not exist). This results in |started| becoming |true|.
A further call of |skip_pages|, with |started| asserted, records the use of
each character. Whilst this ``scanning'' phase is
taking place, the Boolean |scanning| inhibits writing to the \.{TYP} file.
@p procedure skip_pages;
label 9999; {end of this subroutine}
var p:integer; {a parameter}
@!k:0..255; {command code}
@!down_the_drain:integer; {garbage}
@!pages_counted:integer; {counts ``wanted'' pages whilst usage is calculated }
begin
pages_counted:=1; {We've already met the first page requested}
while true do
begin if eof(dvi_file) then bad_dvi('the file ended prematurely');
@:Bad DVI file the file ended prematurely}{\quad\.{the file ended prematurely}@>
k:=get_byte;
p:=first_par(k);
case k of
bop: begin @<Pass a |bop| command, setting up the |count| array@>;
if not started and start_match then
begin started:=true; goto 9999;
end;
if started then
begin incr(pages_counted); {We've got another page that's wanted}
if (pages_counted mod 10)=0 then monitor('.'); {show progress}
if pages_counted>max_pages then {We can skip to the postamble}
begin old_backpointer:=first_backpointer;
move_to_byte(post_loc);
monitor('done.'+crlf); term_offset:=0
end;
end;
end;
set_rule,put_rule: down_the_drain:=signed_quad;
fnt_def1,fnt_def1+1,fnt_def1+2,fnt_def1+3: begin define_font(p);
print_ln(' ');
end;
xxx1,xxx1+1,xxx1+2,xxx1+3: while p>0 do
begin down_the_drain:=get_byte; decr(p);
end;
post: begin in_postamble:=true;
monitor('done.'+crlf); term_offset:=0;
goto 9999;
end;
@<Cases that would set characters on paper@>;
@<Cases that would change fonts@>;
othercases do_nothing
endcases;
end;
9999:end;
@ If (!) we meet a command that would require a character to be set on the
paper, we note that the relevant character has been used, provided the page
being ``skipped'' is one that is to be printed (\\{i.e.} |started| has been
set). The |pages_counted| variable is used to inhibit the gathering of such
statistics after the specified number of pages have been ``read''.
We mark both the individual glyph and the font itself as being |wanted|.
@<Cases that would set characters on paper@>=
sixty_four_cases(set_char_0),sixty_four_cases(set_char_0+64), @/
four_cases(set1),four_cases(put1): @/
if started and (pages_counted<=max_pages) then
begin
cur_font_glyph(p).loaded:=wanted;
font_type[cur_font]:=wanted
end
@ Whenever a font change would have occurred, we determine the |cur_font| so
that the above section, which notes the use of a character, can relate this to
the correct font.
@<Cases that would change fonts@>=
sixty_four_cases(fnt_num_0),four_cases(fnt1): @/
begin font_num[nf]:=p; cur_font:=0;
while font_num[cur_font]<>p do incr(cur_font);
cur_font:=font_map[cur_font];
cur_base:=glyph_base[cur_font]
end
@ Global variables called |old_backpointer| and |new_backpointer|
are used to check whether the back pointers are properly set up.
Another one tells whether we have already found the starting page.
@<Glob...@>=
@!old_backpointer:integer; {the previous |bop| command location}
@!new_backpointer:integer; {the current |bop| command location}
@!started:boolean; {has the starting page been found?}
@ @<Set init...@>=
old_backpointer:=-1; started:=false;
@ @<Pass a |bop|...@>=
new_backpointer:=cur_loc-1; incr(page_count);
for k:=0 to 9 do count[k]:=signed_quad;
if signed_quad<>old_backpointer
then error('backpointer in byte ',cur_loc-4:1,
' should be ',old_backpointer:1,'!');
@:Error: backpointer in byte should be}{\quad\.{backpointer...should be...}@>
old_backpointer:=new_backpointer
@* Using the backpointers.
The routines in this section of the program are brought into play only
if |random_reading| is |true|.
First comes a routine that illustrates how to find the postamble quickly.
@<Find the postamble, working back from the end@>=
n:=dvi_length;
if n<53 then bad_dvi('only ',n:1,' bytes long');
@:Bad DVI file only n bytes long}{\quad\.{only $n$ bytes long}@>
m:=n-1;
repeat if m=0 then bad_dvi('no 223s'); {ignore trailing zeroes}
@:Bad DVI file no 223s}{\quad\.{no 223s}@>
move_to_byte(m); k:=get_byte; decr(m);
until k<>0;
repeat if m=0 then bad_dvi('all 223s');
@:Bad DVI file all 223s}{\quad\.{all 223s}@>
move_to_byte(m); k:=get_byte; decr(m);
until k<>223;
if k<>id_byte then bad_dvi('ID byte is ',k:1);
@:Bad DVI file ID byte is wrong}{\quad\.{ID byte is wrong}@>
move_to_byte(m-3); q:=signed_quad;
if (q<0)or(q>m-33) then bad_dvi('post pointer ',q:1,' at byte ',m-3:1);
@:Bad DVI file post pointer is wrong}{\quad\.{post pointer is wrong}@>
move_to_byte(q); k:=get_byte;
if k<>post then bad_dvi('byte ',q:1,' is not post');
@:Bad DVI file byte n is not post}{\quad\.{byte $n$ is not }\\{post}@>
post_loc:=q; first_backpointer:=signed_quad
@ Note that the last steps of the above code save the locations of the
the |post| byte and the final |bop|. We had better declare these global
variables, together with another one that we will need shortly.
@<Glob...@>=
@!post_loc:integer; {byte location where the postamble begins}
@!first_backpointer:integer; {the pointer following |post|}
@!start_loc:integer; {byte location of the first page to process}
@ The next little routine shows how the backpointers can be followed
to move through a \.{DVI} file in reverse order. Ordinarily a \.{DVI}-reading
program would do this only if it wants to print the pages backwards or
if it wants to find a specified starting page that is not necessarily the
first page in the file; otherwise it would of course be simpler and faster
just to read the whole file from the beginning.
@<Count the pages and move to the starting page@>=
monitor('Finding starting page...');
@.Finding starting page@>
q:=post_loc; p:=first_backpointer; start_loc:=-1;
if p<0 then in_postamble:=true
else begin repeat
{now |q| points to a |post| or |bop| command; |p>=0| is prev pointer}
if p>q-46 then
bad_dvi('page link ',p:1,' after byte ',q:1);
@:Bad DVI file page link wrong}{\quad\.{page link wrong...}@>
q:=p; move_to_byte(q); k:=get_byte;
if k=bop then incr(page_count)
else bad_dvi('byte ',q:1,' is not bop');
@:Bad DVI file byte n is not bop}{\quad\.{byte $n$ is not }\\{bop}@>
for k:=0 to 9 do count[k]:=signed_quad;
if start_match then start_loc:=q;
p:=signed_quad;
@<Note whether we've two \\{recto} pages in succession@>;
until p<0;
if start_loc<0 then abort('starting page number could not be found!');
@:fatal error starting page number}{\quad\.{starting page number...}@>
move_to_byte(start_loc+1); old_backpointer:=start_loc;
for k:=0 to 9 do count[k]:=signed_quad;
p:=signed_quad; started:=true;
end;
if page_count<>total_pages then
warning('there are really ',page_count:1,' pages, not ',total_pages:1,'!');
@:Warning: there are really n pages}{\quad\.{there are really $n$ pages}@>
monitor('found.'+crlf); term_offset:=0
@ One of the reasons for using the above routine (which seems rather
wasteful if the user wants to print pages starting from the beginning of the
\.{DVI} file) is so that we can determine whether any particular \\{recto}
page is followed by another, without an intervening \\{verso} page. This
information can then be used at printing time to change the printing mode of
the DEClaser~2200 (LN06), which supports duplex printing, such that the
first of these pages be printed in simplex mode. Doing this speeds up the
printing cycle compared with merely outputting a blank \\{verso} page,
since the mechanical operation of turning over the paper is thereby avoided.
Therefore we construct an array of pointers into the \.{DVI} file where an
entry indicates that the page is followed by a blank \\{verso} sheet. Here
are the necessary data structures:
@<Glob...@>=
@!blank_follows:array[1..max_blank_pages] of integer;
@!ptr_blanks:0..max_blank_pages;
@!next_is_recto:boolean;
@ Naturally we initialize this such that a final \\{recto} page will have
its blank \\{verso} side treated by the same mechanism.
@<Set init...@>=
next_is_recto:=true; ptr_blanks:=0;
@ As we work our way backwards through the file, we have the opportunity to
recognize that a \\{recto} page is followed directly by another of the same,
without an intervening \\{verso} page. This code relies upon \TeX\ having
used its \.{\\count0} for the page number, which is the usual convention,
even when other counters are used to provide additional information in page
numbering (such as the chapter number). When we later process the
information in the |blank_follows| array, we do so in reverse order, which
therefore corresponds with the processing of the file in the forward
direction.
@<Note whether we've two \\{recto} pages in succession@>=
if odd_page and next_is_recto then
if ptr_blanks=max_blank_pages then
capacity_exceeded('too many blank verso pages')
@:capacity exceeded too many blank verso}{\quad\.{too many blank }\\{verso}\.{ pages}@>
else
begin
incr(ptr_blanks); blank_follows[ptr_blanks]:=cur_loc
end;
next_is_recto:=odd_page
@* Reading the postamble.
Now imagine that we are reading the \.{DVI} file and positioned just
four bytes after the |post| command. That, in fact, is the situation,
when the following part of \.{DVItoLN03} is called upon to read, translate,
and check the rest of the postamble.
@p procedure read_postamble;
var k:integer; {loop index}
@!p,@!q,@!m:integer; {general purpose registers}
begin post_loc:=cur_loc-5;
print_ln('Postamble starts at byte ',post_loc:1,'.');
@.Postamble starts at byte n@>
if signed_quad<>numerator then
warning('numerator doesn''t match the preamble!');
@:warning numerator doesn't match}{\quad\.{numerator doesn't match...}@>
if signed_quad<>denominator then
warning('denominator doesn''t match the preamble!');
@:warning denominator doesn't match}{\quad\.{denominator doesn't match...}@>
if signed_quad<>mag then if new_mag=0 then
warning('magnification doesn''t match the preamble!');
@:warning magnification doesn't match}{\quad\.{magnification doesn't match...}@>
max_v:=signed_quad; max_h:=signed_quad;@/
max_v_saved:=max_v; max_h_saved:=max_h;@/
print('maxv=',max_v:1,', maxh=',max_h:1);@/
max_s:=get_two_bytes; total_pages:=get_two_bytes;@/
print_ln(', maxstackdepth=',max_s:1,', totalpages=',total_pages:1);
@<Process the font definitions of the postamble@>;
@<Make sure that the end of the file is well-formed@>;
end;
@ When we get to the present code, the |post_post| command has
just been read.
@<Make sure that the end of the file is well-formed@>=
q:=signed_quad;
if q<>post_loc then
error('bad postamble pointer in byte ',cur_loc-4:1,'!');
@:Error: bad postamble pointer}{\quad\.{bad postamble pointer}@>
m:=get_byte;
if m<>id_byte then error('identification in byte ',cur_loc-1:1,
@:Error: identification in byte p should be n}{\quad\.{identification...should be $n$}@>
' should be ',id_byte:1,'!');
k:=cur_loc; m:=223;
while (m=223)and not eof(dvi_file) do m:=get_byte;
if not eof(dvi_file) and (m<>0) then bad_dvi('signature in byte ',cur_loc-1:1,
@:Bad DVI file signature...should be}{\quad\.{signature...should be...}@>
' should be 223')
else if cur_loc<k+4 then
warning('not enough signature bytes at end of file (',
@:Warning: not enough signature bytes}{\quad\.{not enough signature bytes...}@>
cur_loc-k:1,')');
@ @<Process the font definitions...@>=
repeat k:=get_byte;
if (k>=fnt_def1)and(k<fnt_def1+4) then
begin p:=first_par(k);
define_font(p);
if not substitution_pass then print_ln(' ');
k:=nop;
end;
until k<>nop;
if k<>post_post then
error('byte ',cur_loc-1:1,' is not postpost!')
@:Error: byte n is not postpost}{\quad\.{byte $n$ is not }\\{postpost}@>
@* The main program.
Now we are ready to put it all together. This is where \.{DVItoLN03} starts,
and where it ends. These macros define various possible exit values for the
program, through the VAX/VMS system service \.{\$exit}.
@^system dependencies@>
@d VAX_exit==@=$exit@>
@d VAX_ss_normal==@= sts$k_success @>
@d VAX_ss_warning==@= sts$k_warning + sts$m_inhib_msg @>
@d VAX_ss_error==@= sts$k_error + sts$m_inhib_msg @>
@d VAX_ss_fatal==@= sts$k_severe + sts$m_inhib_msg @>
@p begin initialize; {get all variables initialized}
dialog; {set up all the options}
write_ln(term_out,'(',actual_file_spec,crlf);
print_ln('Reading from file ',actual_file_spec);
@<Process the preamble@>;
@<Find the postamble, working back from the end@>;
in_postamble:=true; read_postamble; in_postamble:=false;
@<Count the pages and move to the starting page@>;
if not in_postamble then @<Gather font usage statistics@>;
if not in_postamble then @<Translate up to |max_pages| pages@>;
close(ln3_file,@=disposition:=save@>,VAX_continue);
monitor(')');
final_end:
ask := (history > spotless) or odd(VAX_cli_present('LOG'));
if VAX_cli_present('LOG') = VAX_cli_negated then ask := false;
if ask then
close(type_file,@=disposition:=save@>,VAX_continue)
else
close(type_file,@=disposition:=delete@>,VAX_continue);
case history of { Issue an appropriate VAX exit status }
spotless: VAX_exit(VAX_ss_normal); { Everything OK! }
warning_given: VAX_exit(VAX_ss_warning);
error_given: VAX_exit(VAX_ss_error);
fatal_error: VAX_exit(VAX_ss_fatal)
end;
end.
@ The main program needs a few global variables in order to do its work.
@<Glob...@>=
@!k,@!m,@!n,@!p,@!q:integer; {general purpose registers}
@ A \.{DVI}-reading program that reads the postamble first need not look at the
preamble; but \.{DVItoLN03} looks at the preamble in order to do error
checking, and to display the introductory comment.
@<Process the preamble@>=
open_dvi_file;
p:=get_byte; {fetch the first byte}
if p<>pre then bad_dvi('First byte isn''t start of preamble!');
@:Bad DVI file First byte isn\'t}{\quad\.{First byte isn't...}@>
p:=get_byte; {fetch the identification byte}
if p<>id_byte then
error('identification in byte 1 should be ',id_byte:1,'!');
@:Error: identification in byte p should be n}{\quad\.{identification...should be $n$}@>
@<Compute the conversion factor@>;
p:=get_byte; {fetch the length of the introductory comment}
print('''');
while p>0 do
begin decr(p); print(xchr[get_byte]);
end;
print_ln('''')
@ The conversion factor |conv| is figured as follows: There are exactly
|n/d| \.{DVI} units per decimicron, and 254000 decimicrons per inch,
and |resolution| pixels per inch. Then we have to adjust this
by the stated amount of magnification.
@<Compute the conversion factor@>=
numerator:=signed_quad; denominator:=signed_quad;
if numerator<=0 then bad_dvi('numerator is ',numerator:1);
@:Bad DVI file numerator is wrong}{\quad\.{numerator is wrong}@>
if denominator<=0 then bad_dvi('denominator is ',denominator:1);
@:Bad DVI file denominator is wrong}{\quad\.{denominator is wrong}@>
print_ln('numerator/denominator=',numerator:1,'/',denominator:1);
conv:=(numerator/254000.0)*(resolution/denominator);
mag:=signed_quad;
if new_mag>0 then mag:=new_mag
else if mag<=0 then bad_dvi('magnification is ',mag:1);
@:Bad DVI file magnification is wrong}{\quad\.{magnification is wrong}@>
true_conv:=conv; conv:=true_conv*(mag/1000.0);
print_ln('magnification=',mag:1,'; ',conv:16:8,' pixels per DVI unit')
@ The code shown here uses a convention that has proved to be useful: If the
starting page was specified as, e.g., `\.{1.*.-5}', then all page numbers in
the file are displayed by showing the values of counts 0, 1, and~2, separated
by dots. These numbers are displayed on the terminal when DVItoLN03 is working
on that page.
The parameter |-1| in the call of |do_page| indicates to the latter that
this is \&{not} a recursive call of the function, and hence that it should
initialize its data structures for a fresh page.
@<Translate up to...@>=
begin while max_pages>0 do
begin decr(max_pages);
print_ln(' '); print(cur_loc-45:1,': beginning of page ');
text_out:='[';
last_counter:=0;
for k:=0 to 9 do if count[k]<>0 then last_counter:=k;
for k:=0 to last_counter do
begin
if (k=0) or (count[k]<>0) then
begin
print(count[k]:1); text_out:=text_out+str_int(count[k])
end;
if k<last_counter then
begin print('.'); text_out:=text_out+'.' end
else print_ln(' ');
end;
monitor(text_out); {Let user know which page has started}
cur_loc_after_bop:=cur_loc; {For checking on blank \\{verso} pages}
if not do_page(-1) then bad_dvi('page ended unexpectedly');
@:Bad DVI file page ended unexpectedly}{\quad\.{page ended unexpectedly}@>
incr(pages_printed);
repeat k:=get_byte;
if (k>=fnt_def1)and(k<fnt_def1+4) then
begin p:=first_par(k); define_font(p); k:=nop;
end;
until k<>nop;
write_ln(term_out,']'); incr(term_offset); {Let user know page has finished}
if k=post then
begin in_postamble:=true; goto done;
end;
if k<>bop then bad_dvi('byte ',cur_loc-1:1,' is not bop');
@:Bad DVI file byte n is not bop}{\quad\.{byte $n$ is not }\\{bop}@>
@<Pass a |bop|...@>;
end;
done: @<Conditionally eject last page@>
end
@ A string variable |text_out| is provided to hold the
characters of a page number. We also use |last_counter| to indicate the
last significant item in the |count| array when displaying the page number.
@<Globals...@>=
@!text_out : file_spec;
@!last_counter : 0..9;
@ No output for the terminal occurs before the pages are processed except
for error and font load messages, all of which end with a newline. Therefore,
we are quite safe in resetting |term_offset| during the initialisation phase.
@<Set initial...@>=
term_offset:=0;
@* System-dependent changes.
Here are the remaining changes to the program
that are necessary to make \.{DVItoLN03} work on Vax/VMS.
@^system dependencies@>
We define here various variables required by the VAX.
\yskip\hang |history| is used to record whether any warning or error
messages (including fatal errors) have been reported.
\yskip\hang |type_file| and |ln3_file| are |file| variables used for writing
the log and
output files, respectively. Advantage is taken of VAX-\PASCAL's |varying|
arrays to hold variable length strings: |file_name| is used generally for
names of files, whilst |def_file_name| initially holds the specification of
the \.{.dvi} file being processed, from which the name is later extracted for
use in forming the names of generated files.
The remaining variables are used during the parsing of the file
specification.
@<Glob...@>==
@!history: exit_status;
@!type_file: text;
@!ln3_file: text;
@!file_name,@!actual_file_spec,@!log_file_name,
@!output_file_name:file_spec;
@!file_message:file_spec;
@!cmd_j,@!cmd_k:integer;
@!ask : boolean;
@ Here is the enumeration type used for |history|.
@<Types...@>==
@!exit_status = (@!spotless,@!warning_given,@!error_given,@!fatal_error);
@ We now do all the VMS specific things to open the files required, etc. The
\.{DVIfile} is opened with a fixed block length, and with \.{direct} access
method, to permit |random_reading|. A |user_action| procedure is called, to
perform the actual opening of the file; this gives us access to the various
VAX data structures (|FAB|,|RAB| and |XAB|) and thus we are able to determine
the actual length of the file.
The |output| file (directed to the terminal) is opened without
|carriage_control|, in order that |write_ln| may be used to ensure that
each partial line of output is displayed on the screen.
@<Preset init...@>=
open(output,'SYS$OUTPUT',@=carriage_control:=none@>,VAX_continue);
history := spotless; { We haven't had any errors (yet!) }
@<Get name of \.{DVI} file from command-interpreter, and open it@>@;
@<Extract just the file name from the specification of the \.{DVI} file@>@;
@<Extract the |log_file_name| from the \.{/LOG} qualifier@>@;
@<Create and open the \.{TYP} log file@>@;
@<Extract the \TeX\ Font Metric directory from the qualifiers@>;
@<Extract the font directories from the qualifiers@>@;
@<Extract the virtual font directory from the qualifiers@>@;
@<Extract the |output_file_name| from the \.{/OUTPUT} qualifier@>@;
@<Create and open the \.{LN3} output file@>@;
@ When we parse the qualifiers pertaining to the physical font directories,
we use the variable length strings |tex_pxl_font| and |tex_pk_font| to hold
the directory specifications derived from the command line qualifiers
\.{/PXL\_FONT\_DIRECTORY} and \.{/PK\_FONT\_DIRECTORY}, respectively. Iff,
after applying logical name translation (if necessary), a file directory
specification is found to end in the sequence `\.{.]}', then a rooted file
directory structure will be assumed; this is recorded in the variables
|pk_rooted| and |pxl_rooted|.
There is no such complication for the \.{/TFM\_DIRECTORY} qualifier; its
value is merely copied to |tfm_directory|.
@<Glob...@>=
@!tex_pxl_font,@!tex_pk_font,@!tfm_directory:file_spec;
@!pxl_rooted,@!pk_rooted:boolean;
@ Originally, this program had the location of the \TeX\ Font Metrics files
(\.{.TFM}) `hard-wired' into it, just as the VMS implementation of \TeX\
itself did, through the logical name \.{TEX\$FONTS}. \TeX\ has since been
modified so that such information is communicated to the program through a
command-line qualifier, and this program too now makes use of the same
mechanism.
This permits two things:
\yskip\hang$\bullet$ The site manager can chose whether to use logical names
of the form \.{TEX\UL FONTS} or \.{TEX\$FONTS} ({\sl Digital\/} recommends that
customers should \&{not} define logical names containg the `\.\$' character,
since the logicals which they themselves define \&{always} contain this
character, and may at some future date conflict with customer-defined
logicals).
\yskip\hang$\bullet$ Individual users may define their own logical names
that provide a search-list for a number of directories; this can prove
useful whilst testing a new font that should not be installed for general
access.
@<Extract the \TeX\ Font Metric directory from the qualifiers@>=
if odd(VAX_cli_present('TFM_DIRECTORY')) then
k:=VAX_cli_get_value('TFM_DIRECTORY',tfm_directory)
else
tfm_directory:=''
@ At this point we ``read'' the font directory specifications passed as the
values of the qualifiers \.{/PXL\_FONT\_DIRECTORY} and
\.{/PK\_FONT\_DIRECTORY}. At least one of these must be provided with a
value. The specifications are transferred to the variables |tex_pxl_font| and
|tex_pk_font| respectively, and logical name translation is applied. If the
resultant string doesn't end with a `\.]', an error is signalled. If the
character \&{preceding} this bracket is `\..', the final `\.]' is discarded.
@<Extract the font directories from the qualifiers@>==
tex_pk_font:=''; tex_pxl_font:='';
ask:=odd(VAX_cli_present('PK_FONT_DIRECTORY'));
if ask then
begin
VAX_cli_get_value('PK_FONT_DIRECTORY',tex_pk_font);
Translate(tex_pk_font,file_name);
if file_name[file_name.length] <> ']' then
abort('Bad /PK_FONT_DIRECTORY qualifier; doesn''t end with a `]''');
@:fatal error Bad pk_font_directory}{\quad\.{Bad /PK_FONT_DIRECTORY qualifier}@>
pk_rooted := file_name[file_name.length-1] = '.';
end;
if odd(VAX_cli_present('PXL_FONT_DIRECTORY')) then
begin
VAX_cli_get_value('PXL_FONT_DIRECTORY',tex_pxl_font);
Translate(tex_pxl_font,file_name);
if file_name[file_name.length] <> ']' then
abort('Bad /PXL_FONT_DIRECTORY qualifier; doesn''t end with a `]''');
@:fatal error Bad pxl_font_directory}{\quad\.{Bad /PXL_FONT_DIRECTORY qualifier}@>
pxl_rooted := file_name[file_name.length-1] = '.';
ask:=true;
end;
if not ask then abort('Where are the font directories? (/PK_FONT_DIRECTORY, etc)');
@:fatal error Where are the font directories}{\quad\.{Where are the font directories?}@>
@ Similarly, we determine the directory in which virtual font metrics are to
be found; if the qualifier \.{/VIRTUAL\UL DIRECTORY} is negated, then
virtual font support is explicitly suppressed.
@<Extract the virtual font directory from the qualifiers@>==
tex_virtual:='';
i := VAX_cli_present('VIRTUAL_DIRECTORY');
no_virt_support := i = VAX_cli_negated;
if odd(i) then
VAX_cli_get_value('VIRTUAL_DIRECTORY',tex_virtual);
@ The variable length string |tex_virtual| receives this definition. If
\.{/NOVIRTUAL\UL DIRECTORY} has been specified, this is recorded in
|no_virt_support|.
@<Glob...@>=
@!tex_virtual:file_spec;
@!no_virt_support:boolean;
@ We now ask the Vax/VMS command-line interpreter to give us the file
specification furnished by the user to the command which invokes DVItoLN03.
The file is opened \.{readonly} to preclude any possibility of corrupting or
deleting it, and the \.{user\_action} paramter of the VAX-\PASCAL\ |open|
procedure is used, so that we can determine the full file specification and
length of the file, which is also opened for random (\.{direct}) access.
We can't use |abort| for the fatal error reports, because the log file
hasn't yet been opened, so we define this instead:
@d bomb_out(#)==begin write_ln(output,crlf,'Fatal error: ',#,crlf); jump_out end
@<Get name of \.{DVI} file from command-interpreter, and open it@>=
def_file_name:='';
ask:=odd(VAX_cli_get_value('FILESPEC',def_file_name));
if not ask then bomb_out('No file name provided');
@.No file name provided@>
@:fatal error no file name provided}{\quad\.{No file name provided}@>
open(dvi_file,def_file_name,@=readonly@>,@=access_method:=direct@>,
@=user_action@>:=file_open,@=default@>:='.DVI;0',VAX_continue);
ask:=status(dvi_file)<>0; dvi_size:=file_len;
if ask then bomb_out('Couldn''t open ',def_file_name);
@:fatal error couldn\'t open DVI}{\quad\.{Couldn't open .DVI file}@>
actual_file_spec:=def_file_name;
@ The user has the option to specify an explicit file name on the \.{/LOG}
qualifier. If none is specified, or the qualifier is absent, the log file
will be created in the current directory, with the same name as the input
file and a file extension of `\.{.TYP}'. These are also the defaults used
if the relevant portion of the file specification is not given with the
\.{/LOG} qualifier.
@<Extract the |log_file_name| from the \.{/LOG} qualifier@>=
log_file_name:=''; {Null name specified in case defaults required}
if odd(VAX_cli_present('LOG')) then
VAX_cli_get_value('LOG',log_file_name);
@ After we've successfully opened the \.{DVI} file (and are thus assured that
it exists), we strip off any node, device or directory specification from the
front of the string, and also remove any trailing file type or version number
specification. This makes use of the VAX-\PASCAL\ |substr| function, but
could readily be recoded into standard \PASCAL.
@<Extract just the file name from the specification of the \.{DVI} file@>=
cmd_j:=1; {Strip off any leading node, device or directory name}
for cmd_k:=1 to def_file_name.length do
if (def_file_name[cmd_k]=']')
or (def_file_name[cmd_k]=':')
or (def_file_name[cmd_k]='>')
then cmd_j:=cmd_k+1;
if cmd_j<=def_file_name.length then
def_file_name:=substr(def_file_name,cmd_j,def_file_name.length-cmd_j+1);@/
cmd_j:=0; {and then strip off any trailing file type or version indication}
for cmd_k:=1 to def_file_name.length do
if (cmd_j=0) and @/
((def_file_name[cmd_k]='.') or (def_file_name[cmd_k]=';')) then
cmd_j:=cmd_k;
if cmd_j=0 then cmd_j:=def_file_name.length+1;
def_file_name:=substr(def_file_name,1,cmd_j-1);
@ We now |open| the \.{TYP} file in the current directory, to act as a log
file for this run of DVItoLN03. The file is opened with |disposition:=save|
so that it will still `exist' even in the event of a crash; this means that the
user can interrupt the program (with Ctrl-Y) and be assured that the only
file that will be retained is the log.
@<Create and open the \.{TYP} log file@>=
file_name:=def_file_name+'.TYP';
open(type_file,log_file_name,@=new@>,@=32767@>,@=disposition:=save@>,
@=default@>:=file_name,VAX_continue);
ask:=status(type_file)>0;
if ask then bomb_out('Couldn''t create ',file_name);
@:fatal error couldn\'t create TYP}{\quad\.{Couldn't create .TYP file}@>
rewrite(type_file);
@ A particularly useful qualifier for those with limited disk quota is
\.{/OUTPUT}, which permits a user to specify a different output file
specification from the default, which is to open the output file in the
current directory with a name taken from the input (\.{.DVI}) file, and file
extension `\.{.LN3}'. Using this qualifier permits a user to divert the
output to a scratch disk.
@<Extract the |output_file_name| from the \.{/OUTPUT} qualifier@>=
output_file_name := ''; {Prepare to use the default name}
if odd(VAX_cli_present('OUTPUT')) then
VAX_cli_get_value('OUTPUT',output_file_name);
@ Finally, we create and |open| the \.{LN3} file. Again this is opened with
|disposition:=delete|.
@<Create and open the \.{LN3} output file@>=
file_name := def_file_name+'.LN3';
open(ln3_file,output_file_name,@=new@>,@=256@>,@=disposition@>:=@=delete@>,
@=carriage_control@>:=@=none@>,
@=default@>:=file_name,VAX_continue);
ask:=status(ln3_file)>0;
if ask then begin
abort('Couldn''t create ',file_name);
@:fatal error couldn\'t create LN3 file}{\quad\.{Couldn't create .LN3 file}@>
goto final_end
end;
rewrite(ln3_file);
@ Whenever a binary file is opened, the optional |user_action| parameter is
provided to the call of the VAX-\PASCAL\ |open| procedure; this therefore
invokes the procedure |file_open| (in the next section) which is therefore
able to determine the full specification of the file opened, and also its
exact length; the former is placed into |def_file_name|, and the latter in
|file_len|.
The following definitions permit more \PASCAL-like references to the VAX/RMS
data structures within |file_open|
@d VAX_FAB_type == @=fab$type@>
@d VAX_RAB_type == @=rab$type@>
@d VAX_NAM_type == @=nam$type@>
@d VAX_XAB_type == @=xab$type@>
@d VAX_RMS_open == @=$OPEN@>
@d VAX_RMS_connect == @=$CONNECT@>
@d VAX_fab_to_nam == @=fab$l_nam@> {Pointer in FAB to NAM block}
@d VAX_nam_length == @=nam$b_rsl@> {Length of resultant file name}
@d VAX_nam_address == @=nam$l_rsa@> {and its whereabouts}
@d VAX_fab_to_xab == @=fab$l_xab@> {Pointer in FAB to first XAB}
@d VAX_xab_code == @=xab$b_cod@> {Byte in XAB which identifies its type}
@d VAX_xab_fhc == @=xab$c_fhc@> {XAB contains file header characteristics}
@d VAX_xab_next == @=xab$l_nxt@> {Pointer to another XAB}
@d VAX_end_block == @=xab$l_ebk@> {Number of file block containing EOF}
@d VAX_first_free == @=xab$w_ffb@> {First free byte in last block}
@<Glob...@>=
@!def_file_name : file_spec;
@!file_len : integer;
@ Here is the procedure |file_open|, which determines the actual length of
the file being opened. It also extracts the full file specification of the
\.{DVI} which it opens, and reports this back in |def_file_name|.
Vax/VMS uses RMS (Record Management Services) to handle file operations, and
this utilizes various structures, in particular, the FAB (File Access Block)
and the RAB (Record Access Block). There are also a number of different
XABs (eXtension Access Block), one of which contains the required
information relating to the size of the file.
A |user_action| procedure such as this is invoked by the VAX-\PASCAL\ |open|
procedure after it has set up the FAB and RAB structures. The user is
therefore able at that time to change these structures to alter the processing
algorithms used by RMS. The file has \&{not}, however, been opened at that
time; it is necessary to call the RMS \.{\$OPEN} service. Once the RAB has
been completed to the user's satisfaction, the \.{\$CONNECT} service is called
to associate the file variable with the records of the file.
Due to the nonsensical manner in which the |FAB|, etc., are declared in
\.{SYS\$LIBRARY:STARLET.PAS}, wherein the pointers to |XAB|s are declared
to be of type |unsigned|, it is necessary for us to use type casts
for assignments and usage as a pointer!
@<Procedures for init...@>=
function file_open(var @!fab:VAX_FAB_type; var @!rab:VAX_RAB_type;
var @!f_spec:byte_file): integer;
type @!xab_ptr = ^VAX_XAB_type;
@!nam_ptr = ^VAX_NAM_type;
@!char_ptr = ^char;
var @!status:integer;@/
@!nam : nam_ptr;
@!xab : xab_ptr;
@!actual_length : integer;
@!next : char_ptr;
@!i : integer;
begin file_len:=-1; {In case we can't determine actual file size}
status:=VAX_RMS_open(fab); {Fill in the FAB and RAB}
if odd(status) then
begin status:=VAX_RMS_connect(rab); {Link the RAB to RMS}
if odd(status) then
begin
nam:=fab.VAX_fab_to_nam::nam_ptr;
if nam<>nil then
begin
actual_length := nam^.VAX_nam_length;
def_file_name := ''; {Reset to empty}
next:=nam^.VAX_nam_address::char_ptr;
for i:=1 to actual_length do
begin
def_file_name := def_file_name + next^;
next:=(next::integer + 1)::char_ptr;
end;
end;
xab:=fab.VAX_fab_to_xab::xab_ptr;
{Find the File Header Characteristics}
while (xab<>nil) and (xab^.VAX_xab_code<>VAX_xab_fhc) do
xab:=xab^.VAX_xab_next::xab_ptr;
if xab<>nil then
with xab^ do
file_len:=int((VAX_end_block-1)*VAX_block_length+VAX_first_free);
end;
end;
file_open:=status; {Return success or failure}
end; {|file_open|}
@ This function provides us with logical name translation. It utilizes the
\.{SYS\$TRNLNM} system service in preference to the obsolete \.{SYS\$TRNLOG}.
On entry, any terminal `\.:' on the string |log_name| is discarded before
attempting logical name translation. If the latter succeeds, the translation
is written back to |equivalence_string| and the function returns |true|; if
translation fails, the original |log_name| is returned in |equivalence_string| and
|false| returned.
It is necessary to provide the local variable |copy_name| during the
translation process, because VAX-\PASCAL\ does not permit the address of a
|var| parameter to be taken, so we cannot use |equivalence_string| to receive
the successive translations, and yet the address of the string must be passed
to the system service as the buffer to receive the translated logical name.
Similarly, we cannot use |log_name|, since that will be a local copy, so that
|string_size| will be that of the original logical name.
The next macro is used to hide the VAX-\PASCAL\ conformant schema from W{\mc
EAVE}, and thus permit more meaningful formatting.
@d VAX_conformant_schema(#)==VAX_volatile varying [#] of char
@#
@d VAX_trnlnm == @=$TRNLNM@>
@d VAX_lnm_case_blind==@= lnm$m_case_blind@>
@<Procedures for initialization@>==
function Translate(log_name : VAX_conformant_schema(string_size);
var equivalence_string : VAX_conformant_schema(buf_size) ) : boolean;
var @!i, @!status : integer;
@!copy_name : VAX_volatile file_spec;
@!return_length : VAX_volatile integer;
@!attributes : unsigned;
@!item_list : VAX_volatile array [0..1] of VMS_item_list;
begin
equivalence_string := log_name; {In case translation fails}
copy_name := log_name;
if copy_name[copy_name.length] = ':' then
decr(copy_name.length);
attributes := VAX_lnm_case_blind;
return_length := 0;
with item_list[0] do
begin
buffer_length := buf_size;
item_code := @=LNM$_STRING@>;
buffer_addr := @=iaddress@>(copy_name.body);
ret_len_addr := @=iaddress@>(return_length);
end;
item_list[1].next_item := 0;
status := VAX_trnlnm(attributes,'LNM$DCL_LOGICAL',copy_name,,item_list);
if not odd(status) then
Translate := false
else
begin
copy_name.length := return_length;
return_length := 0;
while odd(VAX_trnlnm(attributes,'LNM$DCL_LOGICAL',copy_name,,item_list)) do
begin
copy_name.length := return_length;
return_length := 0;
end;
Translate := true;
equivalence_string := copy_name;
end;
end;
@ Here are the new types introduced within that function. Many Vax/VMS system
services make use of an |item_list| to pass information in and out. An
|item_list| consists of a number of |item_list| elements, with each element
containing the following fields:
\centerline{\vtop{\offinterlineskip\hrule
\halign{\vrule#&\ \strut#\hfil\ &\ #\hfil\ &\ #\hfil\ &\vrule#\cr
height2pt&\omit&\omit&\omit&\cr
&\hfil Name & \hfil Type & \hfil Usage&\cr
height2pt&\omit&\omit&\omit&\cr
\noalign{\hrule}
height2pt&\omit&\omit&\omit&\cr
&|buffer_length| & 16-bit word & Size of buffer&\cr
&|item_code| & unsigned 16-bit word & Code for desired operation&\cr
&|buffer_address| & Pointer to char & Address of buffer&\cr
&|ret_len_addr| & Pointer to integer & To receive length of
translation&\cr
height2pt&\omit&\omit&\omit&\cr}
\hrule
}}\yskip
\noindent This structure is overlaid with a single 32-bit integer whose use is
solely to hold the value zero indicating the end of the list.
@<Types in the...@>==
@!VMS_item_list =
packed record
case boolean of
true: (
@!buffer_length : sixteen_bits;@/
@!item_code : sixteen_bits;@/
@!buffer_addr : integer;@/
@!ret_len_addr : integer);
false: (
@!next_item : integer)
end;
@* Index.
Pointers to error messages appear here together with the section numbers
where each ident\-i\-fier is used.